/CVox/src/com/android/contacts/FastScrollView.java
http://eyes-free.googlecode.com/ · Java · 453 lines · 341 code · 63 blank · 49 comment · 75 complexity · 46eecf134f722326f65649656020489e MD5 · raw file
- /*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- //package com.android.contacts;
- package com.android.contacts;
- import com.cvox.browser.R;
- import android.content.Context;
- import android.content.res.Resources;
- import android.graphics.Canvas;
- import android.graphics.Paint;
- import android.graphics.RectF;
- import android.graphics.drawable.Drawable;
- import android.os.Handler;
- import android.os.SystemClock;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
- import android.view.ViewGroup.OnHierarchyChangeListener;
- import android.widget.AbsListView;
- import android.widget.Adapter;
- import android.widget.BaseAdapter;
- import android.widget.FrameLayout;
- import android.widget.HeaderViewListAdapter;
- import android.widget.ListView;
- import android.widget.AbsListView.OnScrollListener;
- /**
- * FastScrollView is meant for embedding {@link ListView}s that contain a large number of
- * items that can be indexed in some fashion. It displays a special scroll bar that allows jumping
- * quickly to indexed sections of the list in touch-mode. Only one child can be added to this
- * view group and it must be a {@link ListView}, with an adapter that is derived from
- * {@link BaseAdapter}.
- */
- public class FastScrollView extends FrameLayout
- implements OnScrollListener, OnHierarchyChangeListener {
- private Drawable mCurrentThumb;
- private Drawable mOverlayDrawable;
- private int mThumbH;
- private int mThumbW;
- private int mThumbY;
- private RectF mOverlayPos;
-
- // Hard coding these for now
- private int mOverlaySize = 104;
- 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;
- interface SectionIndexer {
- Object[] getSections();
-
- int getPositionForSection(int section);
-
- int getSectionForPosition(int position);
- }
-
- public FastScrollView(Context context) {
- super(context);
- init(context);
- }
- public FastScrollView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- init(context);
- }
- public FastScrollView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context);
- }
- private void useThumbDrawable(Drawable drawable) {
- mCurrentThumb = drawable;
- mThumbW = 64; //mCurrentThumb.getIntrinsicWidth();
- mThumbH = 52; //mCurrentThumb.getIntrinsicHeight();
- mChangedBounds = true;
- }
- private void init(Context context) {
- // Get both the scrollbar states drawables
- final Resources res = context.getResources();
- useThumbDrawable(res.getDrawable(
- R.drawable.scrollbar_handle_accelerated_anim2));
-
- mOverlayDrawable = res.getDrawable(R.drawable.dialog_full_dark);
-
- 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(mOverlaySize / 2);
- 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 FastScrollView.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 + mOverlaySize / 4 - 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 - mOverlaySize) / 2;
- pos.right = pos.left + mOverlaySize;
- pos.top = h / 10; // 10% from top
- pos.bottom = pos.top + mOverlaySize;
- 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 = 255;
- 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();
- }
- }
- }
- }