blob: ace105db713056940b3e688b3fd0454cfee04386 [file] [log] [blame]
/*
* Copyright (C) 2010 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.gallery3d.ui;
import android.content.Context;
import android.graphics.Rect;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.animation.DecelerateInterpolator;
import com.android.gallery3d.anim.Animation;
import com.android.gallery3d.ui.PositionRepository.Position;
import com.android.gallery3d.util.Utils;
import java.util.LinkedHashMap;
public class SlotView extends GLView {
private static final String TAG = "SlotView";
private static final int MAX_VELOCITY = 2500;
private static final int INDEX_NONE = -1;
public static interface Listener {
public void onLayoutChanged(int width, int height);
public void onScrollPositionChanged(int position);
}
public interface SlotTapListener {
public void onSingleTapUp(int index);
public void onLongTap(int index);
}
private final GestureDetector mGestureDetector;
private final ScrollerHelper mScroller = new ScrollerHelper();
private final PositionRepository mPositions;
private SlotTapListener mSlotTapListener;
private Listener mListener;
private int mTransitionOffsetX;
// Use linked hash map to keep the rendering order
private LinkedHashMap<Long, ItemEntry> mItems =
new LinkedHashMap<Long, ItemEntry>();
private MyAnimation mAnimation = null;
private final Position mTempPosition = new Position();
private final Layout mLayout = new Layout();
public SlotView(Context context, PositionRepository repository) {
mPositions = repository;
mGestureDetector =
new GestureDetector(context, new MyGestureListener());
}
public void setSlotSize(int slotWidth, int slotHeight) {
mLayout.setSlotSize(slotWidth, slotHeight);
}
public void setSlotGaps(int horizontalGap, int verticalGap, boolean center) {
mLayout.setSlotGaps(horizontalGap, verticalGap, center);
}
public void setListener(Listener listener) {
mListener = listener;
}
@Override
public void addComponent(GLView view) {
throw new UnsupportedOperationException();
}
@Override
public boolean removeComponent(GLView view) {
throw new UnsupportedOperationException();
}
@Override
protected void onLayout(boolean changeSize, int l, int t, int r, int b) {
if (!changeSize) return;
mLayout.setSize(r - l, b - t);
if (mListener != null) mListener.onLayoutChanged(r - l, b - t);
}
public void startTransition() {
mAnimation = new MyAnimation();
mAnimation.start();
mTransitionOffsetX = mScrollX;
if (mItems.size() != 0) invalidate();
}
public void savePositions() {
mPositions.clear();
for (ItemEntry entry : mItems.values()) {
Position position = entry.target.clone();
position.x -= mScrollX;
mPositions.putPosition(entry.item.getIdentity(), position);
}
}
private void setScrollPosition(int position, boolean force) {
position = Utils.clamp(position, 0, mLayout.mContentLength);
if (!force && position == mScrollX) return;
mScrollX = position;
mLayout.setScrollPosition(position);
if (mListener != null) mListener.onScrollPositionChanged(position);
}
public void putDisplayItem(Position target, DisplayItem item) {
Long identity = Long.valueOf(item.getIdentity());
Position source = mPositions.get(identity);
if (source == null) {
source = target.clone();
source.alpha = 0f;
} else {
source = source.clone();
source.x += mTransitionOffsetX;
}
mItems.put(identity, new ItemEntry(item, source, target));
}
public Rect getSlotRect(int slotIndex) {
return mLayout.getSlotRect(slotIndex);
}
public void removeDisplayItem(DisplayItem item) {
mItems.remove(item.getIdentity());
}
@Override
protected boolean onTouch(MotionEvent event) {
mGestureDetector.onTouchEvent(event);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mScroller.forceFinished();
break;
}
return true;
}
public void setSlotTapListener(SlotTapListener listener) {
mSlotTapListener = listener;
}
@Override
protected void render(GLCanvas canvas) {
super.render(canvas);
long currentTimeMillis = canvas.currentAnimationTimeMillis();
boolean more = mScroller.advanceAnimation(currentTimeMillis);
setScrollPosition(mScroller.getPosition(), false);
float interpolate = 1f;
if (mAnimation != null) {
more |= mAnimation.calculate(currentTimeMillis);
interpolate = mAnimation.value;
}
canvas.translate(-mScrollX, 0, 0);
for (ItemEntry entry : mItems.values()) {
renderItem(canvas, entry, interpolate);
}
canvas.translate(mScrollX, 0, 0);
if (more) invalidate();
}
private void renderItem(
GLCanvas canvas, ItemEntry entry, float interpolate) {
canvas.save(GLCanvas.SAVE_FLAG_ALPHA | GLCanvas.SAVE_FLAG_MATRIX);
Position position = mTempPosition;
Position.interpolate(entry.source, entry.target, position, interpolate);
canvas.multiplyAlpha(position.alpha);
canvas.translate(position.x, position.y, position.z);
canvas.rotate(position.theta, 0, 0, 1);
entry.item.render(canvas);
canvas.restore();
}
public static class MyAnimation extends Animation {
public float value;
public MyAnimation() {
setInterpolator(new DecelerateInterpolator(4));
}
@Override
protected void onCalculate(float progress) {
value = progress;
}
}
private static class ItemEntry {
public DisplayItem item;
public Position source;
public Position target;
public ItemEntry(DisplayItem item, Position source, Position target) {
this.item = item;
this.source = source;
this.target = target;
}
}
public static class Layout {
private int mVisibleStart;
private int mVisibleEnd;
private int mSlotCount;
private int mSlotWidth;
private int mSlotHeight;
private int mWidth;
private int mHeight;
private int mVerticalGap;
private int mHorizontalGap;
private boolean mCenter;
private int mTopMargin;
private int mRowCount;
private int mContentLength;
private int mScrollPosition;
public void setSlotSize(int slotWidth, int slotHeight) {
mSlotWidth = slotWidth;
mSlotHeight = slotHeight;
}
public void setSlotCount(int slotCount) {
mSlotCount = slotCount;
initLayoutParameters();
}
public void setSlotGaps(int horizontalGap, int verticalGap, boolean center) {
mHorizontalGap = horizontalGap;
mVerticalGap = verticalGap;
mCenter = center;
initLayoutParameters();
}
public Rect getSlotRect(int index) {
int col = index / mRowCount;
int row = index - col * mRowCount;
int x = col * (mHorizontalGap + mSlotWidth) + mHorizontalGap;
int y = row * (mVerticalGap + mSlotHeight) + mVerticalGap + mTopMargin;
return new Rect(x, y, x + mSlotWidth, y + mSlotHeight);
}
public int getContentLength() {
return mContentLength;
}
private void initLayoutParameters() {
int rowCount = (mHeight - mVerticalGap) / (mVerticalGap + mSlotHeight);
if (rowCount == 0) rowCount = 1;
mRowCount = rowCount;
if (mCenter) {
mTopMargin = (mHeight - rowCount * (mVerticalGap + mSlotHeight)) / 2;
} else {
mTopMargin = 0;
}
mContentLength = ((mSlotCount + rowCount - 1) / rowCount)
* (mHorizontalGap + mSlotWidth) + mHorizontalGap - mWidth;
if (mContentLength < 0) mContentLength = 0;
updateVisibleSlotRange();
}
public void setSize(int width, int height) {
mWidth = width;
mHeight = height;
initLayoutParameters();
}
private void updateVisibleSlotRange() {
int position = mScrollPosition;
int colWidth = mHorizontalGap + mSlotWidth;
int rowHeight = mVerticalGap + mSlotHeight;
int startColumn = position / colWidth;
int endColumn = (position + mWidth + mSlotWidth - 1) / colWidth;
setVisibleRange(startColumn * mRowCount,
Math.min(mSlotCount, endColumn * mRowCount));
}
public void setScrollPosition(int position) {
if (mScrollPosition == position) return;
mScrollPosition = position;
updateVisibleSlotRange();
}
private void setVisibleRange(int start, int end) {
if (start == mVisibleStart && end == mVisibleEnd) return;
mVisibleStart = start;
mVisibleEnd = end;
}
public int getVisibleStart() {
return mVisibleStart;
}
public int getVisibleEnd() {
return mVisibleEnd;
}
public int getSlotIndexByPosition(float x, float y) {
int columnWidth = mHorizontalGap + mSlotWidth;
float absoluteX = x + mScrollPosition;
int columnIdx = (int) (absoluteX + 0.5) / columnWidth;
if ((absoluteX - columnWidth * columnIdx) < mHorizontalGap) {
return INDEX_NONE;
}
int rowHeight = mVerticalGap + mSlotHeight;
float absoluteY = y - mTopMargin;
int rowIdx = (int) (absoluteY + 0.5) / rowHeight;
if (((absoluteY - rowHeight * rowIdx) < mVerticalGap)
|| rowIdx >= mRowCount) {
return INDEX_NONE;
}
int index = columnIdx * mRowCount + rowIdx;
return index >= mSlotCount ? INDEX_NONE : index;
}
}
private class MyGestureListener
extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onFling(MotionEvent e1,
MotionEvent e2, float velocityX, float velocityY) {
int contentLength = mLayout.mContentLength;
if (contentLength == 0) return false;
velocityX = Utils.clamp(velocityX, -MAX_VELOCITY, MAX_VELOCITY);
mScroller.fling(-velocityX, 0, contentLength);
invalidate();
return true;
}
@Override
public boolean onScroll(MotionEvent e1,
MotionEvent e2, float distanceX, float distanceY) {
if (mLayout.mContentLength == 0) return false;
mScroller.startScroll(
Math.round(distanceX), 0, mLayout.mContentLength);
invalidate();
return true;
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
if (index != INDEX_NONE) mSlotTapListener.onSingleTapUp(index);
return true;
}
@Override
public void onLongPress(MotionEvent e) {
int index = mLayout.getSlotIndexByPosition(e.getX(), e.getY());
if (index != INDEX_NONE) mSlotTapListener.onLongTap(index);
}
}
public void setSlotCount(int slotCount) {
mLayout.setSlotCount(slotCount);
}
public int getVisibleStart() {
return mLayout.getVisibleStart();
}
public int getVisibleEnd() {
return mLayout.getVisibleEnd();
}
}