blob: edc162757d97ed8c7fcd7edc134fac818014b7fb [file] [log] [blame]
package com.android.gallery3d.ui;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View.MeasureSpec;
import android.widget.Scroller;
import com.android.gallery3d.anim.AlphaAnimation;
import com.android.gallery3d.anim.CanvasAnimation;
public class GLListView extends GLView {
@SuppressWarnings("unused")
private static final String TAG = "GLListView";
private static final int INDEX_NONE = -1;
private static final int SCROLL_BAR_TIMEOUT = 2500;
private static final int HIDE_SCROLL_BAR = 1;
private Model mModel;
private final Handler mHandler;
private int mHighlightIndex = INDEX_NONE;
private GLView mHighlightView;
private Texture mHighLight;
private NinePatchTexture mScrollbar;
private int mVisibleStart = 0; // inclusive
private int mVisibleEnd = 0; // exclusive
private boolean mHasMeasured = false;
private boolean mScrollBarVisible = false;
private CanvasAnimation mScrollBarAnimation;
private OnItemSelectedListener mOnItemSelectedListener;
private final GestureDetector mGestureDetector;
private final Scroller mScroller;
private boolean mScrollable;
private boolean mIsPressed = false;
static public interface Model {
public int size();
public GLView getView(int index);
public boolean isSelectable(int index);
}
static public interface OnItemSelectedListener {
public void onItemSelected(GLView view, int position);
}
public GLListView(Context context) {
mScroller = new Scroller(context);
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
GLRootView root = getGLRootView();
if (root != null) {
synchronized (root) {
handleMessageLocked(msg);
}
} else {
handleMessageLocked(msg);
}
}
private void handleMessageLocked(Message msg) {
switch(msg.what) {
case HIDE_SCROLL_BAR:
setScrollBarVisible(false);
break;
}
}
};
mGestureDetector = new GestureDetector(
context, new MyGestureListener(), mHandler);
}
@Override
protected void onVisibilityChanged(int visibility) {
super.onVisibilityChanged(visibility);
if (visibility == GLView.VISIBLE && mScrollHeight > getHeight()) {
setScrollBarVisible(true);
mHandler.sendEmptyMessageDelayed(
HIDE_SCROLL_BAR, SCROLL_BAR_TIMEOUT);
}
}
private void setScrollBarVisible(boolean visible) {
if (mScrollBarVisible == visible || mScrollbar == null) return;
mScrollBarVisible = visible;
if (!visible) {
mScrollBarAnimation = new AlphaAnimation(1, 0);
mScrollBarAnimation.setDuration(300);
mScrollBarAnimation.start();
} else {
mScrollBarAnimation = null;
}
invalidate();
}
public void setHighLight(Texture highLight) {
mHighLight = highLight;
}
public void setDataModel(Model model) {
mModel = model;
mScrollY = 0;
requestLayout();
}
public void setOnItemSelectedListener(OnItemSelectedListener l) {
mOnItemSelectedListener = l;
}
private boolean drawWithAnimation(GLCanvas canvas,
Texture texture, int x, int y, int w, int h, CanvasAnimation anim) {
long now = canvas.currentAnimationTimeMillis();
canvas.save(anim.getCanvasSaveFlags());
boolean more = anim.calculate(canvas.currentAnimationTimeMillis());
anim.apply(canvas);
texture.draw(canvas, x, y, w, h);
canvas.restore();
if (more) invalidate();
return more;
}
@Override
protected void render(GLCanvas canvas) {
canvas.save(GLCanvas.SAVE_FLAG_CLIP);
canvas.clipRect(0, 0, getWidth(), getHeight());
if (mHighlightIndex != INDEX_NONE) {
GLView view = mModel.getView(mHighlightIndex);
Rect bounds = view.bounds();
if (mHighLight != null) {
int width = bounds.width();
int height = bounds.height();
mHighLight.draw(canvas,
bounds.left - mScrollX, bounds.top - mScrollY,
width, height);
}
}
super.render(canvas);
canvas.restore();
if (mScrollBarAnimation != null || mScrollBarVisible) {
int width = mScrollbar.getWidth();
int height = getHeight() * getHeight() / mScrollHeight;
int yoffset = mScrollY * getHeight() / mScrollHeight;
if (mScrollBarAnimation != null) {
if (!drawWithAnimation(
canvas, mScrollbar, getWidth() - width, yoffset,
width, height, mScrollBarAnimation)) {
mScrollBarAnimation = null;
}
} else {
mScrollbar.draw(
canvas, getWidth() - width, yoffset, width, height);
}
}
if (mScroller.computeScrollOffset()) {
setScrollPosition(mScroller.getCurrY(), false);
}
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
// first get the total height
int height = 0;
int maxWidth = 0;
for (int i = 0, n = mModel.size(); i < n; ++i) {
GLView view = mModel.getView(i);
view.measure(widthSpec, MeasureSpec.UNSPECIFIED);
height += view.getMeasuredHeight();
maxWidth = Math.max(maxWidth, view.getMeasuredWidth());
}
mScrollHeight = height;
mHasMeasured = true;
MeasureHelper.getInstance(this)
.setPreferredContentSize(maxWidth, height)
.measure(widthSpec, heightSpec);
}
@Override
public int getComponentCount() {
return mVisibleEnd - mVisibleStart;
}
@Override
public GLView getComponent(int index) {
if (index < 0 || index >= mVisibleEnd - mVisibleStart) {
throw new ArrayIndexOutOfBoundsException(index);
}
return mModel.getView(mVisibleStart + index);
}
@Override
public void requestLayout() {
mHasMeasured = false;
super.requestLayout();
}
@Override
protected void onLayout(
boolean change, int left, int top, int right, int bottom) {
if (!mHasMeasured || mMeasuredWidth != (right - left)) {
measure(makeMeasureSpec(right - left, MeasureSpec.EXACTLY),
makeMeasureSpec(bottom - top, MeasureSpec.EXACTLY));
}
mScrollable = mScrollHeight > (bottom - top);
int width = right - left;
int yoffset = 0;
for (int i = 0, n = mModel.size(); i < n; ++i) {
GLView item = mModel.getView(i);
item.onAddToParent(this);
int nextOffset = yoffset + item.getMeasuredHeight();
item.layout(0, yoffset, width, nextOffset);
yoffset = nextOffset;
}
setScrollPosition(mScrollY, true);
}
private void setScrollPosition(int position, boolean force) {
int height = getHeight();
position = Util.clamp(position, 0, mScrollHeight - height);
if (!force && position == mScrollY) return;
mScrollY = position;
int n = mModel.size();
int start = 0;
int end = 0;
for (start = 0; start < n; ++start) {
if (position < mModel.getView(start).mBounds.bottom) break;
}
int bottom = position + height;
for (end = start; end < n; ++ end) {
if (bottom <= mModel.getView(end).mBounds.top) break;
}
setVisibleRange(start , end);
invalidate();
}
private void setVisibleRange(int start, int end) {
if (start == mVisibleStart && end == mVisibleEnd) return;
mVisibleStart = start;
mVisibleEnd = end;
}
@Override
protected boolean dispatchTouchEvent(MotionEvent event) {
return onTouch(event);
}
@Override @SuppressWarnings("fallthrough")
protected boolean onTouch(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mIsPressed = true;
mHandler.removeMessages(HIDE_SCROLL_BAR);
setScrollBarVisible(mScrollHeight > getHeight());
// fallthrough: we need to highlight the item which is pressed
case MotionEvent.ACTION_MOVE:
if (!mScrollable) {
findAndSetHighlightItem((int) event.getY());
}
break;
case MotionEvent.ACTION_UP:
mIsPressed = false;
if (mScrollBarVisible) {
mHandler.removeMessages(HIDE_SCROLL_BAR);
mHandler.sendEmptyMessageDelayed(
HIDE_SCROLL_BAR, SCROLL_BAR_TIMEOUT);
}
if (!mScrollable && mOnItemSelectedListener != null
&& mHighlightView != null) {
mOnItemSelectedListener
.onItemSelected(mHighlightView, mHighlightIndex);
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_OUTSIDE:
setHighlightItem(null, INDEX_NONE);
}
return true;
}
private void findAndSetHighlightItem(int y) {
int position = y + mScrollY;
for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
GLView child = mModel.getView(i);
if (child.mBounds.bottom > position) {
if (mModel.isSelectable(i)) {
setHighlightItem(child, i);
return;
}
break;
}
}
setHighlightItem(null, INDEX_NONE);
}
private void setHighlightItem(GLView view, int index) {
if (index == mHighlightIndex) return;
mHighlightIndex = index;
mHighlightView = view;
if (mHighLight != null) invalidate();
}
public void setScroller(NinePatchTexture scrollbar) {
this.mScrollbar = scrollbar;
requestLayout();
}
private class MyGestureListener
extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1,
MotionEvent e2, float velocityX, float velocityY) {
if (!mScrollable) return false;
mScroller.fling(0, mScrollY,
0, -(int) velocityY, 0, 0, 0, mScrollHeight - getHeight());
invalidate();
return true;
}
@Override
public boolean onScroll(MotionEvent e1,
MotionEvent e2, float distanceX, float distanceY) {
if (!mScrollable) return false;
setHighlightItem(null, INDEX_NONE);
setScrollPosition(mScrollY + (int) distanceY, false);
return true;
}
@Override
public void onShowPress(MotionEvent e) {
if (!mScrollable || !mIsPressed) return;
findAndSetHighlightItem((int) e.getY());
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
if (!mScrollable) return false;
findAndSetHighlightItem((int) e.getY());
if (mOnItemSelectedListener != null && mHighlightView != null) {
mOnItemSelectedListener
.onItemSelected(mHighlightView, mHighlightIndex);
}
setHighlightItem(null, INDEX_NONE);
return true;
}
}
}