/*
* 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.
*/
/**
* 修改自Android SDK
*/
package jecelyin.android.v2.widget;
import jecelyin.android.v2.text.Selection;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
import android.text.Spannable;
import android.view.MotionEvent;
import android.widget.SectionIndexer;
import com.jecelyin.editor.R;
//import android.util.Log;
/**
* Helper class for AbsListView to draw and control the Fast Scroll thumb
*/
public class FastScroller {
// Minimum number of pages to justify showing a fast scroll thumb
private static int MIN_PAGES = 1;//jecelyin: 超过一页就显示滚动条
// Scroll thumb not showing
private static final int STATE_NONE = 0;
// Not implemented yet - fade-in transition
//private static final int STATE_ENTER = 1;
// Scroll thumb visible and moving along with the scrollbar
private static final int STATE_VISIBLE = 2;
// Scroll thumb being dragged by user
private static final int STATE_DRAGGING = 3;
// Scroll thumb fading out due to inactivity timeout
private static final int STATE_EXIT = 4;
private Drawable mThumbDrawable;
private int mThumbH;
private int mThumbW;
private int mThumbY;
private TextView mList;
//private boolean mScrollCompleted;
private int mVisibleItem;
private Paint mPaint;
private int mItemCount = -1;
private boolean mLongList;
private Object [] mSections;
private ScrollFade mScrollFade;
private int mState;
private Handler mHandler = new Handler();
private SectionIndexer mSectionIndexer;
private boolean mChangedBounds;
// private String TAG = "FastScroller";
public FastScroller(Context context, TextView textView) {
mList = textView;
init(context);
}
public void setState(int state) {
switch (state) {
case STATE_NONE:
mHandler.removeCallbacks(mScrollFade);
mList.invalidate();
break;
case STATE_VISIBLE:
if (mState != STATE_VISIBLE) { // Optimization
resetThumbPos();
}
// Fall through
case STATE_DRAGGING:
mHandler.removeCallbacks(mScrollFade);
break;
case STATE_EXIT:
int viewWidth = mList.getWidth();
mList.invalidate(viewWidth - mThumbW, mThumbY, viewWidth, mThumbY + mThumbH);
break;
}
mState = state;
}
public int getState() {
return mState;
}
private void resetThumbPos() {
final int viewWidth = mList.getWidth();
// Bounds are always top right. Y coordinate get's translated during draw
//Log.v(TAG, "setBounds resetThumbPos left:"+String.valueOf(viewWidth - mThumbW)+" right:"+String.valueOf(viewWidth)+" button:"+String.valueOf(mThumbH));
mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH);
mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX);
}
private void useThumbDrawable(Context context, Drawable drawable) {
mThumbDrawable = drawable;
/*mThumbW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
48, context.getResources().getDisplayMetrics());
mThumbH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
52, context.getResources().getDisplayMetrics());*/
mThumbW = context.getResources().getDimensionPixelSize(
R.dimen.fastscroll_thumb_width);
mThumbH = context.getResources().getDimensionPixelSize(
R.dimen.fastscroll_thumb_height);
mChangedBounds = true;
}
private void init(Context context) {
// Get both the scrollbar states drawables
final Resources res = context.getResources();
useThumbDrawable(context, res.getDrawable(
R.drawable.scrollbar_handle_accelerated_anim2));
//mScrollCompleted = true;
getSectionsFromIndexer();
mScrollFade = new ScrollFade();
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setTextAlign(Paint.Align.CENTER);
TypedArray ta = context.getTheme().obtainStyledAttributes(new int[] {
android.R.attr.textColorPrimary });
ColorStateList textColor = ta.getColorStateList(ta.getIndex(0));
int textColorNormal = textColor.getDefaultColor();
mPaint.setColor(textColorNormal);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mState = STATE_NONE;
}
void stop() {
setState(STATE_NONE);
}
boolean isVisible() {
return !(mState == STATE_NONE);
}
public void draw(Canvas canvas) {
//Log.v(TAG, "draw status "+String.valueOf(mState));
if (mState == STATE_NONE) {
// No need to draw anything
return;
}
final int y = mThumbY + mList.getScrollY();
final int viewWidth = mList.getWidth();
final FastScroller.ScrollFade scrollFade = mScrollFade;
final int x = mList.getScrollX();
int alpha = -1;
if (mState == STATE_EXIT) {
alpha = scrollFade.getAlpha();
if (alpha < ScrollFade.ALPHA_MAX / 2) {
mThumbDrawable.setAlpha(alpha * 2);
}
int left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX;
//Log.v(TAG, "setBounds draw left:"+String.valueOf(left)+" right:"+String.valueOf(viewWidth)+" button:"+String.valueOf(mThumbH));
mThumbDrawable.setBounds(left, 0, viewWidth, mThumbH);
mChangedBounds = true;
}
//Log.v(TAG, "setBounds draw x:"+String.valueOf(x)+" y:"+String.valueOf(y));
canvas.translate(x, y);
mThumbDrawable.draw(canvas);
canvas.translate(-x, -y);
// If user is dragging the scroll bar, draw the alphabet overlay
if (alpha == 0) { // Done with exit
setState(STATE_NONE);
} else {
mList.invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH);
}
}
public void onSizeChanged(int w, int h, int oldw, int oldh) {
if (mThumbDrawable != null) {
//Log.v(TAG, "setBounds onSizeChanged left:"+String.valueOf(w - mThumbW)+" right:"+String.valueOf(w)+" button:"+String.valueOf(mThumbH));
mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH);
}
}
public void onScroll(TextView textView, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
//Log.d(TAG, "onScroll firstVisibleItem:"+String.valueOf(firstVisibleItem)
// +" visibleItemCount:"+String.valueOf(visibleItemCount)
// +" totalItemCount:"+String.valueOf(totalItemCount)
//);
// Are there enough pages to require fast scroll? Recompute only if total count changes
if (mItemCount != totalItemCount && visibleItemCount > 0) {
mItemCount = totalItemCount;
mLongList = mItemCount / visibleItemCount >= MIN_PAGES;
}
//Log.d(TAG, "onScroll mLongList:"+String.valueOf(mLongList)+" mState:"+String.valueOf(mState));
if (!mLongList) {
if (mState != STATE_NONE) {
setState(STATE_NONE);
}
return;
}
if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING ) {
mThumbY = ((mList.getHeight() - mThumbH) * firstVisibleItem)
/ (totalItemCount - visibleItemCount);
if (mChangedBounds) {
resetThumbPos();
mChangedBounds = false;
}
}
//mScrollCompleted = true;
//Log.d(TAG, "onScroll firstVisibleItem:"+String.valueOf(firstVisibleItem)+" mVisibleItem:"+String.valueOf(mVisibleItem));
if (firstVisibleItem == mVisibleItem) {
return;
}
mVisibleItem = firstVisibleItem;
if (mState != STATE_DRAGGING) {
setState(STATE_VISIBLE);
mHandler.postDelayed(mScrollFade, 1500);
}
}
SectionIndexer getSectionIndexer() {
return mSectionIndexer;
}
Object[] getSections() {
if (mSections == null ) {
getSectionsFromIndexer();
}
return mSections;
}
private void getSectionsFromIndexer() {
mSectionIndexer = null;
mSections = new String[] { " " };
}
private void scrollTo(float position) {
int count = mList.getLineCount();
//mScrollCompleted = false;
int index = (int) (position * count);
try {
int offset = mList.getLayout().getLineStart(index);
//Log.v(TAG, "scrollTo:"+String.valueOf(index)+" offset:"+String.valueOf(offset));
Selection.setSelection((Spannable) mList.getText(), offset, offset);
}catch(Exception e) {
}
}
private void cancelFling() {
// Cancel the list fling
MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
mList.onTouchEvent(cancelFling);
cancelFling.recycle();
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
//Log.d(TAG, "onInterceptTouchEvent mState:"+String.valueOf(mState)+" ev.getAction():"+String.valueOf(ev.getAction()));
if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) {
if (isPointInside(ev.getX(), ev.getY())) {
setState(STATE_DRAGGING);
return true;
}
}
return false;
}
public boolean onTouchEvent(MotionEvent me) {
//Log.d(TAG, "onTouchEvent mState:"+String.valueOf(mState));
if (mState == STATE_NONE) {
return false;
}
final int action = me.getAction();
//Log.d(TAG, "onTouchEvent action:"+String.valueOf(action));
if (action == MotionEvent.ACTION_DOWN) {
if (isPointInside(me.getX(), me.getY())) {
setState(STATE_DRAGGING);
if (mSections == null ) {
getSectionsFromIndexer();
}
if (mList != null) {
}
cancelFling();
return true;
}
} else if (action == MotionEvent.ACTION_UP) {
if (mState == STATE_DRAGGING) {
setState(STATE_VISIBLE);
final Handler handler = mHandler;
handler.removeCallbacks(mScrollFade);
handler.postDelayed(mScrollFade, 1000);
return true;
}
} else if (action == MotionEvent.ACTION_MOVE) {
if (mState == STATE_DRAGGING) {
final int viewHeight = mList.getHeight();
// Jitter
int newThumbY = (int) me.getY() - mThumbH / 2;
if (newThumbY < 0) {
newThumbY = 0;
} else if (newThumbY + mThumbH > viewHeight) {
newThumbY = viewHeight - mThumbH;
}
//Log.v(TAG, "onTouchEvent: Math.abs(mThumbY - newThumbY): "+String.valueOf(Math.abs(mThumbY - newThumbY)));
if (Math.abs(mThumbY - newThumbY) < 2) {
return true;
}
mThumbY = newThumbY;
// If the previous scrollTo is still pending
// if (mScrollCompleted) {
scrollTo((float) mThumbY / (viewHeight - mThumbH));
// }
return true;
}
}
return false;
}
boolean isPointInside(float x, float y) {
return x > mList.getWidth() - mThumbW && y >= mThumbY && y <= mThumbY + mThumbH;
}
public class ScrollFade implements Runnable {
long mStartTime;
long mFadeDuration;
static final int ALPHA_MAX = 208;
static final long FADE_DURATION = 200;
void startFade() {
mFadeDuration = FADE_DURATION;
mStartTime = SystemClock.uptimeMillis();
setState(STATE_EXIT);
}
int getAlpha() {
if (getState() != STATE_EXIT) {
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 (getState() != STATE_EXIT) {
startFade();
return;
}
if (getAlpha() > 0) {
mList.invalidate();
} else {
setState(STATE_NONE);
}
}
}
}