| package com.android.camera; |
| |
| import com.android.camera.gallery.IImage; |
| import com.android.camera.gallery.IImageList; |
| |
| import android.content.Context; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.os.Handler; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.GestureDetector; |
| import android.view.KeyEvent; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.GestureDetector.SimpleOnGestureListener; |
| import android.widget.Scroller; |
| |
| class GridViewSpecial extends View { |
| public static final int ORIGINAL_SELECT = -2; |
| public static final int REMOVE_SELCTION = -1; |
| |
| private static final String TAG = "GridViewSpecial"; |
| private IImageList mAllImages = ImageManager.emptyImageList(); |
| |
| ImageBlockManager mImageBlockManager; |
| private Handler mHandler; |
| |
| private LayoutSpec mCurrentSpec; |
| boolean mShowSelection = false; |
| int mCurrentSelection = -1; |
| private boolean mCurrentSelectionPressed; |
| |
| private Listener mListener = null; |
| private DrawAdapter mDrawAdapter = null; |
| |
| long mVideoSizeLimit; |
| private boolean mRunning = false; |
| |
| class LayoutSpec { |
| LayoutSpec(int cols, int w, int h, int leftEdgePadding, |
| int rightEdgePadding, int intercellSpacing) { |
| mColumns = cols; |
| mCellWidth = w; |
| mCellHeight = h; |
| mLeftEdgePadding = leftEdgePadding; |
| mRightEdgePadding = rightEdgePadding; |
| mCellSpacing = intercellSpacing; |
| } |
| int mColumns; |
| int mCellWidth, mCellHeight; |
| int mLeftEdgePadding, mRightEdgePadding; |
| int mCellSpacing; |
| } |
| |
| private final LayoutSpec [] mCellSizeChoices = new LayoutSpec[] { |
| new LayoutSpec(0, 67, 67, 14, 14, 8), |
| new LayoutSpec(0, 92, 92, 14, 14, 8), |
| }; |
| private int mSizeChoice = 1; |
| |
| private int mMaxScrollY; |
| |
| private final boolean mFling = true; |
| private Scroller mScroller = null; |
| |
| private GestureDetector mGestureDetector; |
| private boolean mLayoutComplete = false; |
| |
| public void setListener(Listener listener) { |
| mListener = listener; |
| } |
| |
| public void setDrawAdapter(DrawAdapter adapter) { |
| mDrawAdapter = adapter; |
| } |
| |
| public static interface DrawAdapter { |
| public void drawImage(Canvas canvas, IImage image, |
| Bitmap b, int xPos, int yPos, int w, int h); |
| } |
| |
| public void invalidateImage(int index) { |
| mImageBlockManager.invalidateImage(index); |
| } |
| |
| public void invalidateAllImages() { |
| this.clearCache(); |
| mImageBlockManager = new ImageBlockManager(); |
| mImageBlockManager.moveDataWindow(true); |
| } |
| |
| private void init(Context context) { |
| |
| setVerticalScrollBarEnabled(true); |
| initializeScrollbars(context.obtainStyledAttributes( |
| android.R.styleable.View)); |
| |
| mGestureDetector = new GestureDetector(context, |
| new SimpleOnGestureListener() { |
| @Override |
| public boolean onDown(MotionEvent e) { |
| if (mScroller != null && !mScroller.isFinished()) { |
| mScroller.forceFinished(true); |
| return false; |
| } |
| |
| int pos = computeSelectedIndex(e.getX(), e.getY()); |
| if (pos >= 0 && pos < mAllImages.getCount()) { |
| select(pos, true); |
| } else { |
| select(REMOVE_SELCTION, false); |
| } |
| if (mImageBlockManager != null) { |
| mImageBlockManager.repaintSelection(mCurrentSelection); |
| } |
| invalidate(); |
| return true; |
| } |
| |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, |
| float velocityX, float velocityY) { |
| final float maxVelocity = 2500; |
| if (velocityY > maxVelocity) { |
| velocityY = maxVelocity; |
| } else if (velocityY < -maxVelocity) { |
| velocityY = -maxVelocity; |
| } |
| |
| select(REMOVE_SELCTION, false); |
| if (mFling) { |
| mScroller = new Scroller(getContext()); |
| mScroller.fling(0, mScrollY, 0, -(int) velocityY, 0, 0, 0, |
| mMaxScrollY); |
| computeScroll(); |
| } |
| return true; |
| } |
| |
| @Override |
| public void onLongPress(MotionEvent e) { |
| performLongClick(); |
| } |
| |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, |
| float distanceX, float distanceY) { |
| select(REMOVE_SELCTION, false); |
| scrollBy(0, (int) distanceY); |
| invalidate(); |
| return true; |
| } |
| |
| @Override |
| public boolean onSingleTapUp(MotionEvent e) { |
| select(mCurrentSelection, false); |
| int index = computeSelectedIndex(e.getX(), e.getY()); |
| if (index >= 0 && index < mAllImages.getCount()) { |
| if (mListener != null) mListener.onSelect(index); |
| return true; |
| } |
| return false; |
| } |
| }); |
| // mGestureDetector.setIsLongpressEnabled(false); |
| } |
| |
| public static interface Listener { |
| public void onSelect(int index); |
| public void onLayout(); |
| public void onScroll(int index); |
| } |
| |
| public GridViewSpecial(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| init(context); |
| } |
| |
| public GridViewSpecial(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| init(context); |
| } |
| |
| public GridViewSpecial(Context context) { |
| super(context); |
| init(context); |
| } |
| |
| public void setImageList(IImageList list) { |
| this.mAllImages = list; |
| } |
| |
| @Override |
| protected int computeVerticalScrollRange() { |
| return mMaxScrollY + getHeight(); |
| } |
| |
| public void setSizeChoice(int choice, int scrollY) { |
| mSizeChoice = choice; |
| clearCache(); |
| scrollTo(0, scrollY); |
| requestLayout(); |
| invalidate(); |
| } |
| |
| /** |
| * |
| * @param newSel -2 means use old selection, -1 means remove selection |
| * @param newPressed |
| */ |
| public void select(int newSel, boolean newPressed) { |
| if (newSel == -2) { |
| newSel = mCurrentSelection; |
| } |
| int oldSel = mCurrentSelection; |
| if ((oldSel == newSel) && (mCurrentSelectionPressed == newPressed)) { |
| return; |
| } |
| |
| mShowSelection = (newSel != REMOVE_SELCTION); |
| mCurrentSelection = newSel; |
| mCurrentSelectionPressed = newPressed; |
| if (mImageBlockManager != null) { |
| mImageBlockManager.repaintSelection(oldSel); |
| mImageBlockManager.repaintSelection(newSel); |
| } |
| |
| if (newSel != REMOVE_SELCTION) { |
| ensureVisible(newSel); |
| } |
| } |
| |
| public void scrollToImage(int index) { |
| Rect r = getRectForPosition(index); |
| scrollTo(0, r.top); |
| } |
| |
| private void ensureVisible(int pos) { |
| Rect r = getRectForPosition(pos); |
| int top = getScrollY(); |
| int bot = top + getHeight(); |
| |
| if (r.bottom > bot) { |
| mScroller = new Scroller(getContext()); |
| mScroller.startScroll(mScrollX, mScrollY, 0, |
| r.bottom - getHeight() - mScrollY, 200); |
| computeScroll(); |
| } else if (r.top < top) { |
| mScroller = new Scroller(getContext()); |
| mScroller.startScroll(mScrollX, mScrollY, 0, r.top - mScrollY, 200); |
| computeScroll(); |
| } |
| invalidate(); |
| } |
| |
| public void start() { |
| mRunning = true; |
| requestLayout(); |
| } |
| |
| public void stop() { |
| mScroller = null; |
| clearCache(); |
| mRunning = false; |
| } |
| |
| public void clearCache() { |
| if (mImageBlockManager != null) { |
| mImageBlockManager.onPause(); |
| mImageBlockManager = null; |
| } |
| } |
| |
| @Override |
| public void onLayout(boolean changed, int left, int top, |
| int right, int bottom) { |
| super.onLayout(changed, left, top, right, bottom); |
| if (!mRunning) { |
| return; |
| } |
| clearCache(); |
| |
| mCurrentSpec = mCellSizeChoices[mSizeChoice]; |
| LayoutSpec spec = mCurrentSpec; |
| |
| int oldColumnCount = spec.mColumns; |
| int width = right - left; |
| |
| spec.mColumns = 1 + (width - spec.mCellWidth) |
| / (spec.mCellWidth + spec.mCellSpacing); |
| |
| spec.mLeftEdgePadding = (width |
| - ((spec.mColumns - 1) * spec.mCellSpacing) |
| - (spec.mColumns * spec.mCellWidth)) / 2; |
| spec.mRightEdgePadding = spec.mLeftEdgePadding; |
| |
| int rows = (mAllImages.getCount() + spec.mColumns - 1) |
| / spec.mColumns; |
| mMaxScrollY = spec.mCellSpacing |
| + (rows |
| * (spec.mCellSpacing + spec.mCellHeight)) |
| - (bottom - top) ; |
| if (mImageBlockManager == null) { |
| mImageBlockManager = new ImageBlockManager(); |
| mImageBlockManager.moveDataWindow(true); |
| } |
| mLayoutComplete = true; |
| if (mListener != null) mListener.onLayout(); |
| } |
| |
| class ImageBlockManager { |
| private final ImageLoader mLoader; |
| private int mBlockCacheFirstBlockNumber = 0; |
| |
| // mBlockCache is an array with a starting point which is not |
| // necessarily zero. The first element of the array is indicated by |
| // mBlockCacheStartOffset. |
| private int mBlockCacheStartOffset = 0; |
| private ImageBlock [] mBlockCache; |
| |
| private static final int sRowsPerPage = 6; // should compute this |
| |
| private static final int sPagesPreCache = 2; |
| private static final int sPagesPostCache = 2; |
| |
| private int mWorkCounter = 0; |
| private boolean mDone = false; |
| |
| private Thread mWorkerThread; |
| |
| ImageBlockManager() { |
| mLoader = new ImageLoader(mHandler, 1); |
| |
| mBlockCache = new ImageBlock[sRowsPerPage |
| * (sPagesPreCache + sPagesPostCache + 1)]; |
| for (int i = 0; i < mBlockCache.length; i++) { |
| mBlockCache[i] = new ImageBlock(); |
| } |
| |
| mWorkerThread = new Thread(new Runnable() { |
| public void run() { |
| while (true) { |
| int workCounter; |
| synchronized (ImageBlockManager.this) { |
| workCounter = mWorkCounter; |
| } |
| if (mDone) { |
| if (mLoader != null) { |
| mLoader.stop(); |
| } |
| if (mBlockCache != null) { |
| for (int i = 0; i < mBlockCache.length; i++) { |
| ImageBlock block = mBlockCache[i]; |
| if (block != null) { |
| block.recycleBitmaps(); |
| mBlockCache[i] = null; |
| } |
| } |
| } |
| mBlockCache = null; |
| mBlockCacheStartOffset = 0; |
| mBlockCacheFirstBlockNumber = 0; |
| |
| break; |
| } |
| |
| loadNext(); |
| |
| synchronized (ImageBlockManager.this) { |
| if ((workCounter == mWorkCounter) && (!mDone)) { |
| try { |
| ImageBlockManager.this.wait(); |
| } catch (InterruptedException ex) { |
| } |
| } |
| } |
| } // while |
| } // run |
| }); |
| BitmapManager.instance().allowThreadDecoding(mWorkerThread); |
| mWorkerThread.setName("image-block-manager"); |
| mWorkerThread.start(); |
| } |
| |
| public void invalidateImage(int index) { |
| ImageBlock block = getBlockForPos(index); |
| LayoutSpec spec = mCurrentSpec; |
| int column = spec.mColumns; |
| int blockIndex = index / column; |
| int base = blockIndex * column; |
| int col = index - base; |
| int spacing = spec.mCellSpacing; |
| final int yPos = spacing; |
| final int xPos = spec.mLeftEdgePadding |
| + (col * (spec.mCellWidth + spacing)); |
| |
| IImage image = mAllImages.getImageAt(index); |
| if (image != null) { |
| block.loadImage(base, col, image, xPos, yPos); |
| } |
| } |
| |
| private ImageBlock getBlockForPos(int pos) { |
| synchronized (ImageBlockManager.this) { |
| int blockNumber = pos / mCurrentSpec.mColumns; |
| int delta = blockNumber - mBlockCacheFirstBlockNumber; |
| if (delta >= 0 && delta < mBlockCache.length) { |
| int index = (mBlockCacheStartOffset + delta) |
| % mBlockCache.length; |
| ImageBlock b = mBlockCache[index]; |
| return b; |
| } |
| } |
| return null; |
| } |
| |
| private void repaintSelection(int pos) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlock b = getBlockForPos(pos); |
| if (b != null) { |
| b.repaintSelection(); |
| } |
| } |
| } |
| |
| private void onPause() { |
| synchronized (ImageBlockManager.this) { |
| mDone = true; |
| ImageBlockManager.this.notify(); |
| } |
| if (mWorkerThread != null) { |
| try { |
| BitmapManager.instance() |
| .cancelThreadDecoding(mWorkerThread); |
| mWorkerThread.join(); |
| mWorkerThread = null; |
| } catch (InterruptedException ex) { |
| // |
| } |
| } |
| Log.v(TAG, "/ImageBlockManager.onPause"); |
| } |
| |
| synchronized void getVisibleRange(int [] range) { |
| int blockLength = mBlockCache.length; |
| boolean lookingForStart = true; |
| ImageBlock prevBlock = null; |
| for (int i = 0; i < blockLength; i++) { |
| int index = (mBlockCacheStartOffset + i) % blockLength; |
| ImageBlock block = mBlockCache[index]; |
| if (lookingForStart) { |
| if (block.mIsVisible) { |
| range[0] = block.mBlockNumber |
| * mCurrentSpec.mColumns; |
| lookingForStart = false; |
| } |
| } else { |
| if (!block.mIsVisible || i == blockLength - 1) { |
| range[1] = (prevBlock.mBlockNumber |
| * mCurrentSpec.mColumns) |
| + mCurrentSpec.mColumns - 1; |
| break; |
| } |
| } |
| prevBlock = block; |
| } |
| } |
| |
| private void loadNext() { |
| final int blockHeight = (mCurrentSpec.mCellSpacing |
| + mCurrentSpec.mCellHeight); |
| |
| final int firstVisBlock = |
| Math.max(0, (mScrollY - mCurrentSpec.mCellSpacing) |
| / blockHeight); |
| final int lastVisBlock = |
| (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) |
| / blockHeight; |
| |
| synchronized (ImageBlockManager.this) { |
| ImageBlock [] blocks = mBlockCache; |
| int numBlocks = blocks.length; |
| int first = (mBlockCacheStartOffset |
| + (firstVisBlock - mBlockCacheFirstBlockNumber)) |
| % blocks.length; |
| for (int i = 0; i < numBlocks; i++) { |
| int j = first + i; |
| if (j >= numBlocks) { |
| j -= numBlocks; |
| } |
| ImageBlock b = blocks[j]; |
| if (b.startLoading() > 0) { |
| break; |
| } |
| } |
| } |
| } |
| |
| private void moveDataWindow(boolean forceRefresh) { |
| final int blockHeight = (mCurrentSpec.mCellSpacing |
| + mCurrentSpec.mCellHeight); |
| |
| final int firstVisBlock = (mScrollY - mCurrentSpec.mCellSpacing) |
| / blockHeight; |
| final int lastVisBlock = |
| (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) |
| / blockHeight; |
| |
| final int preCache = sPagesPreCache; |
| final int startBlock = Math.max(0, |
| firstVisBlock - (preCache * sRowsPerPage)); |
| |
| synchronized (ImageBlockManager.this) { |
| boolean any = false; |
| ImageBlock [] blocks = mBlockCache; |
| int numBlocks = blocks.length; |
| |
| int delta = startBlock - mBlockCacheFirstBlockNumber; |
| |
| mBlockCacheFirstBlockNumber = startBlock; |
| if (Math.abs(delta) > numBlocks || forceRefresh) { |
| for (int i = 0; i < numBlocks; i++) { |
| int blockNum = startBlock + i; |
| blocks[i].setStart(blockNum); |
| any = true; |
| } |
| mBlockCacheStartOffset = 0; |
| } else if (delta > 0) { |
| mBlockCacheStartOffset += delta; |
| if (mBlockCacheStartOffset >= numBlocks) { |
| mBlockCacheStartOffset -= numBlocks; |
| } |
| |
| for (int i = delta; i > 0; i--) { |
| int index = (mBlockCacheStartOffset + numBlocks - i) |
| % numBlocks; |
| int blockNum = mBlockCacheFirstBlockNumber |
| + numBlocks - i; |
| blocks[index].setStart(blockNum); |
| any = true; |
| } |
| } else if (delta < 0) { |
| mBlockCacheStartOffset += delta; |
| if (mBlockCacheStartOffset < 0) { |
| mBlockCacheStartOffset += numBlocks; |
| } |
| |
| for (int i = 0; i < -delta; i++) { |
| int index = (mBlockCacheStartOffset + i) % numBlocks; |
| int blockNum = mBlockCacheFirstBlockNumber + i; |
| blocks[index].setStart(blockNum); |
| any = true; |
| } |
| } |
| |
| for (int i = 0; i < numBlocks; i++) { |
| int index = (mBlockCacheStartOffset + i) % numBlocks; |
| ImageBlock block = blocks[index]; |
| int blockNum = block.mBlockNumber; |
| boolean isVis = blockNum >= firstVisBlock |
| && blockNum <= lastVisBlock; |
| block.setVisible(isVis); |
| } |
| |
| if (any) { |
| ImageBlockManager.this.notify(); |
| mWorkCounter += 1; |
| } |
| } |
| } |
| |
| void doDraw(Canvas canvas) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlockManager.ImageBlock [] blocks = mBlockCache; |
| int blockCount = 0; |
| |
| if (blocks[0] == null) { |
| return; |
| } |
| |
| final int thisHeight = getHeight(); |
| final int thisWidth = getWidth(); |
| final int height = blocks[0].mBitmap.getHeight(); |
| final int scrollPos = mScrollY; |
| |
| int currentBlock = (scrollPos < 0) |
| ? ((scrollPos - height + 1) / height) |
| : (scrollPos / height); |
| Paint paint = new Paint(); |
| while (true) { |
| final int yPos = currentBlock * height; |
| if (yPos >= scrollPos + thisHeight) { |
| break; |
| } |
| |
| if (currentBlock < 0) { |
| canvas.drawRect(0, yPos, thisWidth, 0, paint); |
| currentBlock += 1; |
| continue; |
| } |
| int effectiveOffset = |
| (mBlockCacheStartOffset |
| + (currentBlock++ - mBlockCacheFirstBlockNumber)) |
| % blocks.length; |
| if (effectiveOffset < 0 |
| || effectiveOffset >= blocks.length) { |
| break; |
| } |
| |
| ImageBlock block = blocks[effectiveOffset]; |
| if (block == null) { |
| break; |
| } |
| synchronized (block) { |
| Bitmap b = block.mBitmap; |
| if (b == null) { |
| break; |
| } |
| canvas.drawBitmap(b, 0, yPos, paint); |
| blockCount += 1; |
| } |
| } |
| } |
| } |
| |
| int blockHeight() { |
| return mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight; |
| } |
| |
| private class ImageBlock { |
| Drawable mCellOutline; |
| Bitmap mBitmap = Bitmap.createBitmap(getWidth(), blockHeight(), |
| Bitmap.Config.RGB_565); |
| Canvas mCanvas = new Canvas(mBitmap); |
| Paint mPaint = new Paint(); |
| |
| int mBlockNumber; |
| |
| // columns which have been requested to the loader |
| int mRequestedMask; |
| |
| // columns which have been completed from the loader |
| int mCompletedMask; |
| boolean mIsVisible; |
| |
| public synchronized void dump( |
| StringBuilder line1, StringBuilder line2) { |
| line2.append(mCompletedMask != 0xF ? 'L' : '_'); |
| line1.append(mIsVisible ? 'V' : ' '); |
| } |
| |
| ImageBlock() { |
| mPaint.setTextSize(14F); |
| mPaint.setStyle(Paint.Style.FILL); |
| |
| mBlockNumber = REMOVE_SELCTION; |
| mCellOutline = GridViewSpecial.this.getResources() |
| .getDrawable(android.R.drawable.gallery_thumb); |
| } |
| |
| private void recycleBitmaps() { |
| synchronized (ImageBlock.this) { |
| mBitmap.recycle(); |
| mBitmap = null; |
| } |
| } |
| |
| private void cancelExistingRequests() { |
| synchronized (ImageBlock.this) { |
| for (int i = 0; i < mCurrentSpec.mColumns; i++) { |
| int mask = (1 << i); |
| if ((mRequestedMask & mask) != 0) { |
| int pos = |
| (mBlockNumber * mCurrentSpec.mColumns) + i; |
| if (mLoader.cancel(mAllImages.getImageAt(pos))) { |
| mRequestedMask &= ~mask; |
| } |
| } |
| } |
| } |
| } |
| |
| private void setStart(final int blockNumber) { |
| synchronized (ImageBlock.this) { |
| if (blockNumber == mBlockNumber) { |
| return; |
| } |
| |
| cancelExistingRequests(); |
| |
| mBlockNumber = blockNumber; |
| mRequestedMask = 0; |
| mCompletedMask = 0; |
| mCanvas.drawColor(0xFF000000); |
| mPaint.setColor(0xFFDDDDDD); |
| int imageNumber = blockNumber * mCurrentSpec.mColumns; |
| int lastImageNumber = mAllImages.getCount() - 1; |
| |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| |
| final int yPos = spacing; |
| |
| for (int col = 0; col < mCurrentSpec.mColumns; col++) { |
| if (imageNumber++ >= lastImageNumber) { |
| break; |
| } |
| final int xPos = leftSpacing |
| + (col * (mCurrentSpec.mCellWidth + spacing)); |
| mCanvas.drawRect(xPos, yPos, |
| xPos + mCurrentSpec.mCellWidth, |
| yPos + mCurrentSpec.mCellHeight, mPaint); |
| paintSel(0, xPos, yPos); |
| } |
| } |
| } |
| |
| private boolean setVisible(boolean isVis) { |
| synchronized (ImageBlock.this) { |
| boolean retval = mIsVisible != isVis; |
| mIsVisible = isVis; |
| return retval; |
| } |
| } |
| |
| private synchronized int startLoading() { |
| final int startRow = mBlockNumber; |
| int count = mAllImages.getCount(); |
| |
| if (startRow == -1) { |
| return 0; |
| } |
| |
| if ((startRow * mCurrentSpec.mColumns) >= count) { |
| return 0; |
| } |
| |
| int retVal = 0; |
| int base = (mBlockNumber * mCurrentSpec.mColumns); |
| for (int col = 0; col < mCurrentSpec.mColumns; col++) { |
| if ((mCompletedMask & (1 << col)) != 0) { |
| continue; |
| } |
| |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| final int yPos = spacing; |
| final int xPos = leftSpacing |
| + (col * (mCurrentSpec.mCellWidth + spacing)); |
| |
| int pos = base + col; |
| if (pos >= count) { |
| break; |
| } |
| |
| IImage image = mAllImages.getImageAt(pos); |
| if (image != null) { |
| loadImage(base, col, image, xPos, yPos); |
| retVal += 1; |
| } |
| } |
| return retVal; |
| } |
| |
| Bitmap resizeBitmap(Bitmap b) { |
| // assume they're both square for now |
| if (b == null || (b.getWidth() == mCurrentSpec.mCellWidth |
| && b.getHeight() == mCurrentSpec.mCellHeight)) { |
| return b; |
| } |
| float scale = (float) mCurrentSpec.mCellWidth |
| / (float) b.getWidth(); |
| Matrix m = new Matrix(); |
| m.setScale(scale, scale, b.getWidth(), b.getHeight()); |
| Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), |
| b.getHeight(), m, false); |
| return b2; |
| } |
| |
| private void drawBitmap( |
| IImage image, int index, Bitmap b, int xPos, int yPos) { |
| mCanvas.setBitmap(mBitmap); |
| if (mDrawAdapter != null) { |
| mDrawAdapter.drawImage(mCanvas, image, b, xPos, yPos, |
| mCurrentSpec.mCellWidth, mCurrentSpec.mCellHeight); |
| } |
| paintSel(index, xPos, yPos); |
| } |
| |
| private void repaintSelection() { |
| int count = mAllImages.getCount(); |
| int startPos = mBlockNumber * mCurrentSpec.mColumns; |
| synchronized (ImageBlock.this) { |
| for (int i = 0; i < mCurrentSpec.mColumns; i++) { |
| int pos = startPos + i; |
| |
| if (pos >= count) { |
| break; |
| } |
| |
| int row = 0; // i / mCurrentSpec.mColumns; |
| int col = i - (row * mCurrentSpec.mColumns); |
| |
| // this is duplicated from getOrKick |
| // (TODO: don't duplicate this code) |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| final int yPos = spacing |
| + (row * (mCurrentSpec.mCellHeight + spacing)); |
| final int xPos = leftSpacing |
| + (col * (mCurrentSpec.mCellWidth + spacing)); |
| |
| paintSel(pos, xPos, yPos); |
| } |
| } |
| } |
| |
| private void paintSel(int pos, int xPos, int yPos) { |
| int[] stateSet = EMPTY_STATE_SET; |
| if (pos == mCurrentSelection && mShowSelection) { |
| if (mCurrentSelectionPressed) { |
| stateSet = PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; |
| } else { |
| stateSet = ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; |
| } |
| } |
| |
| mCellOutline.setState(stateSet); |
| mCanvas.setBitmap(mBitmap); |
| mCellOutline.setBounds(xPos, yPos, |
| xPos + mCurrentSpec.mCellWidth, |
| yPos + mCurrentSpec.mCellHeight); |
| mCellOutline.draw(mCanvas); |
| } |
| |
| private synchronized void loadImage( |
| final int base, |
| final int baseOffset, |
| final IImage image, |
| final int xPos, |
| final int yPos) { |
| final int startBlock = mBlockNumber; |
| final int pos = base + baseOffset; |
| final ImageLoader.LoadedCallback r = |
| new ImageLoader.LoadedCallback() { |
| public void run(Bitmap b) { |
| boolean more = false; |
| synchronized (ImageBlock.this) { |
| if (startBlock != mBlockNumber || mBitmap == null) { |
| return; |
| } |
| |
| drawBitmap(image, pos, b, xPos, yPos); |
| |
| int mask = (1 << baseOffset); |
| mRequestedMask &= ~mask; |
| mCompletedMask |= mask; |
| |
| if (mRequestedMask == 0) { |
| if (mIsVisible) { |
| postInvalidate(); |
| } |
| more = true; |
| } |
| } |
| if (b != null) { |
| b.recycle(); |
| } |
| |
| if (more) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlockManager.this.notify(); |
| mWorkCounter += 1; |
| } |
| } |
| } |
| }; |
| mRequestedMask |= (1 << baseOffset); |
| mLoader.getBitmap(image, pos, r, mIsVisible, false); |
| } |
| } |
| } |
| |
| public void init(Handler handler) { |
| mHandler = handler; |
| } |
| |
| @Override |
| public void onDraw(Canvas canvas) { |
| super.onDraw(canvas); |
| if (mImageBlockManager != null) { |
| mImageBlockManager.doDraw(canvas); |
| mImageBlockManager.moveDataWindow(false); |
| } |
| } |
| |
| @Override |
| public void computeScroll() { |
| if (mScroller != null) { |
| boolean more = mScroller.computeScrollOffset(); |
| scrollTo(0, mScroller.getCurrY()); |
| if (more) { |
| postInvalidate(); // So we draw again |
| } else { |
| mScroller = null; |
| } |
| } else { |
| super.computeScroll(); |
| } |
| } |
| |
| // Return the rectange for the thumbnail in the given position. |
| Rect getRectForPosition(int pos) { |
| LayoutSpec spec = this.mCurrentSpec; |
| int row = pos / spec.mColumns; |
| int col = pos - (row * spec.mColumns); |
| |
| int left = spec.mLeftEdgePadding |
| + (col * spec.mCellWidth) + (col * spec.mCellSpacing); |
| int top = (row * spec.mCellHeight) + (row * spec.mCellSpacing); |
| |
| return new Rect(left, top, |
| left + spec.mCellWidth + spec.mCellSpacing, |
| top + spec.mCellHeight + spec.mCellSpacing); |
| } |
| |
| int computeSelectedIndex(float x, float y) { |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| |
| int row = (int) (mScrollY + y - spacing) |
| / (mCurrentSpec.mCellHeight + spacing); |
| int col = Math.min(mCurrentSpec.mColumns - 1, |
| (int) (x - leftSpacing) / (mCurrentSpec.mCellWidth + spacing)); |
| return (row * mCurrentSpec.mColumns) + col; |
| } |
| |
| @Override |
| public boolean onTouchEvent(MotionEvent ev) { |
| if (!canHandleEvent()) { |
| return false; |
| } |
| mGestureDetector.onTouchEvent(ev); |
| return true; |
| } |
| |
| @Override |
| public void scrollBy(int x, int y) { |
| scrollTo(x, mScrollY + y); |
| } |
| |
| @Override |
| public void scrollTo(int x, int y) { |
| y = Math.min(mMaxScrollY, y); |
| y = Math.max(0, y); |
| if (mListener != null && mCurrentSpec != null) { |
| int index = Math.min(mAllImages.getCount(), |
| Math.max(0, computeSelectedIndex(x, y))); |
| mListener.onScroll(index); |
| } |
| super.scrollTo(x, y); |
| } |
| |
| private boolean canHandleEvent() { |
| return mRunning && mLayoutComplete; |
| } |
| |
| private final Runnable mLongPressCallback = new Runnable() { |
| public void run() { |
| select(GridViewSpecial.ORIGINAL_SELECT, false); |
| showContextMenu(); |
| } |
| }; |
| |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| if (!canHandleEvent()) return false; |
| |
| boolean handled = true; |
| int sel = mCurrentSelection; |
| int columns = mCurrentSpec.mColumns; |
| int count = mAllImages.getCount(); |
| boolean pressed = false; |
| if (mShowSelection) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DPAD_RIGHT: |
| if (sel != count && (sel % columns < columns - 1)) { |
| sel += 1; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_LEFT: |
| if (sel > 0 && (sel % columns != 0)) { |
| sel -= 1; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_UP: |
| if ((sel / columns) != 0) { |
| sel -= columns; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| if ((sel / columns) != (sel + columns / columns)) { |
| sel = Math.min(count - 1, sel + columns); |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_CENTER: |
| pressed = true; |
| mHandler.postDelayed(mLongPressCallback, |
| ViewConfiguration.getLongPressTimeout()); |
| break; |
| default: |
| handled = false; |
| break; |
| } |
| } else { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DPAD_RIGHT: |
| case KeyEvent.KEYCODE_DPAD_LEFT: |
| case KeyEvent.KEYCODE_DPAD_UP: |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| int [] range = new int[2]; |
| if (mImageBlockManager != null) { |
| mImageBlockManager.getVisibleRange(range); |
| int topPos = range[0]; |
| Rect r = getRectForPosition(topPos); |
| if (r.top < getScrollY()) { |
| topPos += columns; |
| } |
| topPos = Math.min(count - 1, topPos); |
| sel = topPos; |
| } |
| break; |
| default: |
| handled = false; |
| break; |
| } |
| } |
| if (handled) { |
| select(sel, pressed); |
| return true; |
| } else { |
| return super.onKeyDown(keyCode, event); |
| } |
| } |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (!canHandleEvent()) return false; |
| |
| if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { |
| select(GridViewSpecial.ORIGINAL_SELECT, false); |
| |
| // The keyUp doesn't get called when the longpress menu comes up. We |
| // only get here when the user lets go of the center key before the |
| // longpress menu comes up. |
| mHandler.removeCallbacks(mLongPressCallback); |
| |
| // open the photo |
| if (mListener != null) mListener.onSelect(mCurrentSelection); |
| return true; |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| } |