2010-07-11 14 views
25

Ho un elenco di eventi che sono separati per mese e anno (giugno 2010, luglio 2010 ecc.). Ho attivato lo scorrimento veloce perché l'elenco è molto lungo. Ho anche implementato SectionIndexer in modo che le persone possano vedere quale mese e anno sono attualmente visualizzati quando si scorre l'elenco degli eventi alla velocità.Problema di scorrimento veloce con ListAdapter e SectionIndexer

Non ho alcun problema con l'implementazione, solo come viene mostrata l'informazione. Lo scorrimento veloce con SectionIndexer sembra essere davvero in grado di supportare un'etichetta con una singola lettera. Se la lista fosse alfabetica, sarebbe perfetta, tuttavia voglio che mostri un po 'più di testo.

Se guardate lo screenshot qui sotto vedrete il problema che sto avendo.

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

Quello che voglio sapere è: è possibile cambiare la modalità di visualizzazione del testo al centro dello schermo. Posso cambiarlo in qualche modo per farlo sembrare giusto (con lo sfondo che copre tutto il testo).

Grazie in anticipo. Se hai bisogno di chiarimenti o codice basta chiedere.

risposta

24

EDIT: codice di esempio completo per questa soluzione disponibile here.

Ho avuto lo stesso problema: avevo bisogno di visualizzare il testo completo nel rettangolo di sovrapposizione anziché solo un singolo carattere. Sono riuscito a risolverlo utilizzando il seguente codice di esempio: http://code.google.com/p/apps-for-android/source/browse/trunk/RingsExtended/src/com/example/android/rings_extended/FastScrollView.java

l'autore ha detto che questo è stato copiato dalla applicazione Contatti, che utilizza a quanto pare la propria implementazione piuttosto che solo l'impostazione fastScrollEnabled="true" sul ListView. L'ho modificato un po 'in modo da poter personalizzare la larghezza del rettangolo sovrapposto, sovrapporre l'altezza del rettangolo, sovrapporre le dimensioni del testo e scorrere la larghezza del pollice.

Per la cronaca, il risultato finale assomiglia a questo: http://nolanwlawson.files.wordpress.com/2011/03/pokedroid_1.png

Tutto quello che dovete fare è aggiungere questi valori alla res/Valori/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> 

E quindi utilizzare questo CustomFastScrollView invece di quello nel 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(); 
      } 
     } 
    } 
} 

È inoltre possibile modificare la traslucenza della casella di scorrimento con ALPHA_MAX.

Poi mettere qualcosa di simile nel layout file xml:

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

Non dimenticare di dichiarare i tuoi attributi in quel file XML di layout così:

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

Avrete anche bisogno per afferrare i drawable R.drawable.scrollbar_handle_accelerated_anim2 da quel codice sorgente Android. Il link sopra contiene solo l'mdpi.

+1

Perfetto. Grazie per questo! :) – matto1990

+0

Il mio problema è quando l'aggiornamento della mia scheda, SectionIndexer non è aggiornato. Come posso farlo? –

+0

Ricorda che se stai cercando di utilizzarlo in una libreria Android, potresti avere problemi con gli attributi personalizzati a causa di questo errore: http://code.google.com/p/android/issues/detail?id= 9656 # makechanges Per aggirare il problema, è necessario copiare il file di layout incluso il componente personalizzato nell'app che utilizza la libreria e quindi modificare l'attributo xmlns nella parte superiore per utilizzare lo spazio dei nomi dell'applicazione anziché quello della libreria. – johnwayner

2

Il widget FastScroller è responsabile del disegno dell'overlay. Probabilmente si dovrebbe dare un'occhiata alla sua fonte:
https://android.googlesource.com/platform/frameworks/base/+/gingerbread-release/core/java/android/widget/FastScroller.java

Ricerca per un commento:

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

Ho visto anche quel bit. Come dovrei fare per renderlo così posso cambiare il modo in cui FastScroller funziona? AbsListView non fornisce alcun modo per modificare quale FastScroller viene utilizzato (memorizzato nel membro privato mFastScroller). Sarei in grado di sovrascrivere tutti i metodi in AbsListView in cui è impostato mFastScroller e quindi cambiarlo in MyFastScroller o soemthing. Dovrei quindi solo fare una copia di FastScroller.java sotto il mio pacchetto e apportare le modifiche di cui ho bisogno perché è una classe privata. Sarebbe bello se Android fornisse un modo per farlo facilmente ;-) – matto1990

+0

Sì, purtroppo molte parti di Android richiedono ancora la copia/estensione manuale piuttosto che un accesso gradevole tramite API. Il modo in cui hai descritto suona come la strada da percorrere. – TalkLittle

+0

Ho iniziato e ci sono un sacco di codice (e drawable) che devono essere sostituiti. Continuerò a farlo finché non avrò qualcosa di gestibile. Se avrò il codice per sembrare mezzo decente (potrei provare ad estendere la classe ListView per aggiungere tutte le funzionalità) rilascerò il codice perché penserei che questo sia qualcosa di cui alcune persone avrebbero bisogno ad un certo punto. Grazie per l'aiuto TalkLittle! – matto1990

Problemi correlati