GridViewSpecial.java revision 666ea1b28a76aeba74744148b15099254d918671
1666ea1b28a76aeba74744148b15099254d918671Owen Lin/*
2666ea1b28a76aeba74744148b15099254d918671Owen Lin * Copyright (C) 2009 The Android Open Source Project
3666ea1b28a76aeba74744148b15099254d918671Owen Lin *
4666ea1b28a76aeba74744148b15099254d918671Owen Lin * Licensed under the Apache License, Version 2.0 (the "License");
5666ea1b28a76aeba74744148b15099254d918671Owen Lin * you may not use this file except in compliance with the License.
6666ea1b28a76aeba74744148b15099254d918671Owen Lin * You may obtain a copy of the License at
7666ea1b28a76aeba74744148b15099254d918671Owen Lin *
8666ea1b28a76aeba74744148b15099254d918671Owen Lin *      http://www.apache.org/licenses/LICENSE-2.0
9666ea1b28a76aeba74744148b15099254d918671Owen Lin *
10666ea1b28a76aeba74744148b15099254d918671Owen Lin * Unless required by applicable law or agreed to in writing, software
11666ea1b28a76aeba74744148b15099254d918671Owen Lin * distributed under the License is distributed on an "AS IS" BASIS,
12666ea1b28a76aeba74744148b15099254d918671Owen Lin * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13666ea1b28a76aeba74744148b15099254d918671Owen Lin * See the License for the specific language governing permissions and
14666ea1b28a76aeba74744148b15099254d918671Owen Lin * limitations under the License.
15666ea1b28a76aeba74744148b15099254d918671Owen Lin */
16666ea1b28a76aeba74744148b15099254d918671Owen Lin
17666ea1b28a76aeba74744148b15099254d918671Owen Linpackage com.android.camera;
18666ea1b28a76aeba74744148b15099254d918671Owen Lin
19666ea1b28a76aeba74744148b15099254d918671Owen Linimport static com.android.camera.Util.Assert;
20666ea1b28a76aeba74744148b15099254d918671Owen Lin
21666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.app.Activity;
22666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.content.Context;
23666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Bitmap;
24666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Canvas;
25666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Paint;
26666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.Rect;
27666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.graphics.drawable.Drawable;
28666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.media.AudioManager;
29666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.os.Handler;
30666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.util.AttributeSet;
31666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.util.DisplayMetrics;
32666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.GestureDetector;
33666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.KeyEvent;
34666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.MotionEvent;
35666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.View;
36666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.ViewConfiguration;
37666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.view.GestureDetector.SimpleOnGestureListener;
38666ea1b28a76aeba74744148b15099254d918671Owen Linimport android.widget.Scroller;
39666ea1b28a76aeba74744148b15099254d918671Owen Lin
40666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.gallery.IImage;
41666ea1b28a76aeba74744148b15099254d918671Owen Linimport com.android.camera.gallery.IImageList;
42666ea1b28a76aeba74744148b15099254d918671Owen Lin
43666ea1b28a76aeba74744148b15099254d918671Owen Linimport java.util.HashMap;
44666ea1b28a76aeba74744148b15099254d918671Owen Lin
45666ea1b28a76aeba74744148b15099254d918671Owen Linclass GridViewSpecial extends View {
46666ea1b28a76aeba74744148b15099254d918671Owen Lin    @SuppressWarnings("unused")
47666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final String TAG = "GridViewSpecial";
48666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final float MAX_FLING_VELOCITY = 2500;
49666ea1b28a76aeba74744148b15099254d918671Owen Lin
50666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static interface Listener {
51666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onImageClicked(int index);
52666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onImageTapped(int index);
53666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onLayoutComplete(boolean changed);
54666ea1b28a76aeba74744148b15099254d918671Owen Lin
55666ea1b28a76aeba74744148b15099254d918671Owen Lin        /**
56666ea1b28a76aeba74744148b15099254d918671Owen Lin         * Invoked when the <code>GridViewSpecial</code> scrolls.
57666ea1b28a76aeba74744148b15099254d918671Owen Lin         *
58666ea1b28a76aeba74744148b15099254d918671Owen Lin         * @param scrollPosition the position of the scroller in the range
59666ea1b28a76aeba74744148b15099254d918671Owen Lin         *         [0, 1], when 0 means on the top and 1 means on the buttom
60666ea1b28a76aeba74744148b15099254d918671Owen Lin         */
61666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onScroll(float scrollPosition);
62666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
63666ea1b28a76aeba74744148b15099254d918671Owen Lin
64666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static interface DrawAdapter {
65666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void drawImage(Canvas canvas, IImage image,
66666ea1b28a76aeba74744148b15099254d918671Owen Lin                Bitmap b, int xPos, int yPos, int w, int h);
67666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void drawDecoration(Canvas canvas, IImage image,
68666ea1b28a76aeba74744148b15099254d918671Owen Lin                int xPos, int yPos, int w, int h);
69666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean needsDecoration();
70666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
71666ea1b28a76aeba74744148b15099254d918671Owen Lin
72666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int INDEX_NONE = -1;
73666ea1b28a76aeba74744148b15099254d918671Owen Lin
74666ea1b28a76aeba74744148b15099254d918671Owen Lin    // There are two cell size we will use. It can be set by setSizeChoice().
75666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The mLeftEdgePadding fields is filled in onLayout(). See the comments
76666ea1b28a76aeba74744148b15099254d918671Owen Lin    // in onLayout() for details.
77666ea1b28a76aeba74744148b15099254d918671Owen Lin    static class LayoutSpec {
78666ea1b28a76aeba74744148b15099254d918671Owen Lin        LayoutSpec(int w, int h, int intercellSpacing, int leftEdgePadding,
79666ea1b28a76aeba74744148b15099254d918671Owen Lin                DisplayMetrics metrics) {
80666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellWidth = dpToPx(w, metrics);
81666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellHeight = dpToPx(h, metrics);
82666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCellSpacing = dpToPx(intercellSpacing, metrics);
83666ea1b28a76aeba74744148b15099254d918671Owen Lin            mLeftEdgePadding = dpToPx(leftEdgePadding, metrics);
84666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
85666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mCellWidth, mCellHeight;
86666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mCellSpacing;
87666ea1b28a76aeba74744148b15099254d918671Owen Lin        int mLeftEdgePadding;
88666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
89666ea1b28a76aeba74744148b15099254d918671Owen Lin
90666ea1b28a76aeba74744148b15099254d918671Owen Lin    private LayoutSpec [] mCellSizeChoices;
91666ea1b28a76aeba74744148b15099254d918671Owen Lin
92666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void initCellSize() {
93666ea1b28a76aeba74744148b15099254d918671Owen Lin        Activity a = (Activity) getContext();
94666ea1b28a76aeba74744148b15099254d918671Owen Lin        DisplayMetrics metrics = new DisplayMetrics();
95666ea1b28a76aeba74744148b15099254d918671Owen Lin        a.getWindowManager().getDefaultDisplay().getMetrics(metrics);
96666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCellSizeChoices = new LayoutSpec[] {
97666ea1b28a76aeba74744148b15099254d918671Owen Lin            new LayoutSpec(67, 67, 8, 0, metrics),
98666ea1b28a76aeba74744148b15099254d918671Owen Lin            new LayoutSpec(92, 92, 8, 0, metrics),
99666ea1b28a76aeba74744148b15099254d918671Owen Lin        };
100666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
101666ea1b28a76aeba74744148b15099254d918671Owen Lin
102666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Converts dp to pixel.
103666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static int dpToPx(int dp, DisplayMetrics metrics) {
104666ea1b28a76aeba74744148b15099254d918671Owen Lin        return (int) (metrics.density * dp);
105666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
106666ea1b28a76aeba74744148b15099254d918671Owen Lin
107666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in init().
108666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Handler mHandler = new Handler();
109666ea1b28a76aeba74744148b15099254d918671Owen Lin    private GestureDetector mGestureDetector;
110666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageBlockManager mImageBlockManager;
111666ea1b28a76aeba74744148b15099254d918671Owen Lin
112666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in set*() functions.
113666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageLoader mLoader;
114666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Listener mListener = null;
115666ea1b28a76aeba74744148b15099254d918671Owen Lin    private DrawAdapter mDrawAdapter = null;
116666ea1b28a76aeba74744148b15099254d918671Owen Lin    private IImageList mAllImages = ImageManager.makeEmptyImageList();
117666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mSizeChoice = 1;  // default is big cell size
118666ea1b28a76aeba74744148b15099254d918671Owen Lin
119666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are set in onLayout().
120666ea1b28a76aeba74744148b15099254d918671Owen Lin    private LayoutSpec mSpec;
121666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mColumns;
122666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mMaxScrollY;
123666ea1b28a76aeba74744148b15099254d918671Owen Lin
124666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We can handle events only if onLayout() is completed.
125666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean mLayoutComplete = false;
126666ea1b28a76aeba74744148b15099254d918671Owen Lin
127666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Selection state
128666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCurrentSelection = INDEX_NONE;
129666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCurrentPressState = 0;
130666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int TAPPING_FLAG = 1;
131666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int CLICKING_FLAG = 2;
132666ea1b28a76aeba74744148b15099254d918671Owen Lin
133666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are cached derived information.
134666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mCount;  // Cache mImageList.getCount();
135666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mRows;  // Cache (mCount + mColumns - 1) / mColumns
136666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mBlockHeight; // Cache mSpec.mCellSpacing + mSpec.mCellHeight
137666ea1b28a76aeba74744148b15099254d918671Owen Lin
138666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean mRunning = false;
139666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Scroller mScroller = null;
140666ea1b28a76aeba74744148b15099254d918671Owen Lin
141666ea1b28a76aeba74744148b15099254d918671Owen Lin    public GridViewSpecial(Context context, AttributeSet attrs) {
142666ea1b28a76aeba74744148b15099254d918671Owen Lin        super(context, attrs);
143666ea1b28a76aeba74744148b15099254d918671Owen Lin        init(context);
144666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
145666ea1b28a76aeba74744148b15099254d918671Owen Lin
146666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void init(Context context) {
147666ea1b28a76aeba74744148b15099254d918671Owen Lin        setVerticalScrollBarEnabled(true);
148666ea1b28a76aeba74744148b15099254d918671Owen Lin        initializeScrollbars(context.obtainStyledAttributes(
149666ea1b28a76aeba74744148b15099254d918671Owen Lin                android.R.styleable.View));
150666ea1b28a76aeba74744148b15099254d918671Owen Lin        mGestureDetector = new GestureDetector(context,
151666ea1b28a76aeba74744148b15099254d918671Owen Lin                new MyGestureDetector());
152666ea1b28a76aeba74744148b15099254d918671Owen Lin        setFocusableInTouchMode(true);
153666ea1b28a76aeba74744148b15099254d918671Owen Lin        initCellSize();
154666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
155666ea1b28a76aeba74744148b15099254d918671Owen Lin
156666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mRedrawCallback = new Runnable() {
157666ea1b28a76aeba74744148b15099254d918671Owen Lin                public void run() {
158666ea1b28a76aeba74744148b15099254d918671Owen Lin                    invalidate();
159666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
160666ea1b28a76aeba74744148b15099254d918671Owen Lin            };
161666ea1b28a76aeba74744148b15099254d918671Owen Lin
162666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setLoader(ImageLoader loader) {
163666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
164666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLoader = loader;
165666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
166666ea1b28a76aeba74744148b15099254d918671Owen Lin
167666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setListener(Listener listener) {
168666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
169666ea1b28a76aeba74744148b15099254d918671Owen Lin        mListener = listener;
170666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
171666ea1b28a76aeba74744148b15099254d918671Owen Lin
172666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setDrawAdapter(DrawAdapter adapter) {
173666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
174666ea1b28a76aeba74744148b15099254d918671Owen Lin        mDrawAdapter = adapter;
175666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
176666ea1b28a76aeba74744148b15099254d918671Owen Lin
177666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setImageList(IImageList list) {
178666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
179666ea1b28a76aeba74744148b15099254d918671Owen Lin        mAllImages = list;
180666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCount = mAllImages.getCount();
181666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
182666ea1b28a76aeba74744148b15099254d918671Owen Lin
183666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setSizeChoice(int choice) {
184666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mRunning == false);
185666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mSizeChoice == choice) return;
186666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSizeChoice = choice;
187666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
188666ea1b28a76aeba74744148b15099254d918671Owen Lin
189666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
190666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void onLayout(boolean changed, int left, int top,
191666ea1b28a76aeba74744148b15099254d918671Owen Lin                         int right, int bottom) {
192666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.onLayout(changed, left, top, right, bottom);
193666ea1b28a76aeba74744148b15099254d918671Owen Lin
194666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!mRunning) {
195666ea1b28a76aeba74744148b15099254d918671Owen Lin            return;
196666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
197666ea1b28a76aeba74744148b15099254d918671Owen Lin
198666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec = mCellSizeChoices[mSizeChoice];
199666ea1b28a76aeba74744148b15099254d918671Owen Lin
200666ea1b28a76aeba74744148b15099254d918671Owen Lin        int width = right - left;
201666ea1b28a76aeba74744148b15099254d918671Owen Lin
202666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The width is divided into following parts:
203666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
204666ea1b28a76aeba74744148b15099254d918671Owen Lin        // LeftEdgePadding CellWidth (CellSpacing CellWidth)* RightEdgePadding
205666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
206666ea1b28a76aeba74744148b15099254d918671Owen Lin        // We determine number of cells (columns) first, then the left and right
207666ea1b28a76aeba74744148b15099254d918671Owen Lin        // padding are derived. We make left and right paddings the same size.
208666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
209666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The height is divided into following parts:
210666ea1b28a76aeba74744148b15099254d918671Owen Lin        //
211666ea1b28a76aeba74744148b15099254d918671Owen Lin        // CellSpacing (CellHeight CellSpacing)+
212666ea1b28a76aeba74744148b15099254d918671Owen Lin
213666ea1b28a76aeba74744148b15099254d918671Owen Lin        mColumns = 1 + (width - mSpec.mCellWidth)
214666ea1b28a76aeba74744148b15099254d918671Owen Lin                / (mSpec.mCellWidth + mSpec.mCellSpacing);
215666ea1b28a76aeba74744148b15099254d918671Owen Lin
216666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec.mLeftEdgePadding = (width
217666ea1b28a76aeba74744148b15099254d918671Owen Lin                - ((mColumns - 1) * mSpec.mCellSpacing)
218666ea1b28a76aeba74744148b15099254d918671Owen Lin                - (mColumns * mSpec.mCellWidth)) / 2;
219666ea1b28a76aeba74744148b15099254d918671Owen Lin
220666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRows = (mCount + mColumns - 1) / mColumns;
221666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockHeight = mSpec.mCellSpacing + mSpec.mCellHeight;
222666ea1b28a76aeba74744148b15099254d918671Owen Lin        mMaxScrollY = mSpec.mCellSpacing + (mRows * mBlockHeight)
223666ea1b28a76aeba74744148b15099254d918671Owen Lin                - (bottom - top);
224666ea1b28a76aeba74744148b15099254d918671Owen Lin
225666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Put mScrollY in the valid range. This matters if mMaxScrollY is
226666ea1b28a76aeba74744148b15099254d918671Owen Lin        // changed. For example, orientation changed from portrait to landscape.
227666ea1b28a76aeba74744148b15099254d918671Owen Lin        mScrollY = Math.max(0, Math.min(mMaxScrollY, mScrollY));
228666ea1b28a76aeba74744148b15099254d918671Owen Lin
229666ea1b28a76aeba74744148b15099254d918671Owen Lin        generateOutlineBitmap();
230666ea1b28a76aeba74744148b15099254d918671Owen Lin
231666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mImageBlockManager != null) {
232666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.recycle();
233666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
234666ea1b28a76aeba74744148b15099254d918671Owen Lin
235666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager = new ImageBlockManager(mHandler, mRedrawCallback,
236666ea1b28a76aeba74744148b15099254d918671Owen Lin                mAllImages, mLoader, mDrawAdapter, mSpec, mColumns, width,
237666ea1b28a76aeba74744148b15099254d918671Owen Lin                mOutline[OUTLINE_EMPTY]);
238666ea1b28a76aeba74744148b15099254d918671Owen Lin
239666ea1b28a76aeba74744148b15099254d918671Owen Lin        mListener.onLayoutComplete(changed);
240666ea1b28a76aeba74744148b15099254d918671Owen Lin
241666ea1b28a76aeba74744148b15099254d918671Owen Lin        moveDataWindow();
242666ea1b28a76aeba74744148b15099254d918671Owen Lin
243666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLayoutComplete = true;
244666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
245666ea1b28a76aeba74744148b15099254d918671Owen Lin
246666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
247666ea1b28a76aeba74744148b15099254d918671Owen Lin    protected int computeVerticalScrollRange() {
248666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mMaxScrollY + getHeight();
249666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
250666ea1b28a76aeba74744148b15099254d918671Owen Lin
251666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We cache the three outlines from NinePatch to Bitmap to speed up
252666ea1b28a76aeba74744148b15099254d918671Owen Lin    // drawing. The cache must be updated if the cell size is changed.
253666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_EMPTY = 0;
254666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_PRESSED = 1;
255666ea1b28a76aeba74744148b15099254d918671Owen Lin    public static final int OUTLINE_SELECTED = 2;
256666ea1b28a76aeba74744148b15099254d918671Owen Lin
257666ea1b28a76aeba74744148b15099254d918671Owen Lin    public Bitmap mOutline[] = new Bitmap[3];
258666ea1b28a76aeba74744148b15099254d918671Owen Lin
259666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void generateOutlineBitmap() {
260666ea1b28a76aeba74744148b15099254d918671Owen Lin        int w = mSpec.mCellWidth;
261666ea1b28a76aeba74744148b15099254d918671Owen Lin        int h = mSpec.mCellHeight;
262666ea1b28a76aeba74744148b15099254d918671Owen Lin
263666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = 0; i < mOutline.length; i++) {
264666ea1b28a76aeba74744148b15099254d918671Owen Lin            mOutline[i] = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
265666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
266666ea1b28a76aeba74744148b15099254d918671Owen Lin
267666ea1b28a76aeba74744148b15099254d918671Owen Lin        Drawable cellOutline;
268666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline = GridViewSpecial.this.getResources()
269666ea1b28a76aeba74744148b15099254d918671Owen Lin                .getDrawable(android.R.drawable.gallery_thumb);
270666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setBounds(0, 0, w, h);
271666ea1b28a76aeba74744148b15099254d918671Owen Lin        Canvas canvas = new Canvas();
272666ea1b28a76aeba74744148b15099254d918671Owen Lin
273666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_EMPTY]);
274666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(EMPTY_STATE_SET);
275666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
276666ea1b28a76aeba74744148b15099254d918671Owen Lin
277666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_PRESSED]);
278666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(
279666ea1b28a76aeba74744148b15099254d918671Owen Lin                PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET);
280666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
281666ea1b28a76aeba74744148b15099254d918671Owen Lin
282666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.setBitmap(mOutline[OUTLINE_SELECTED]);
283666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.setState(ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET);
284666ea1b28a76aeba74744148b15099254d918671Owen Lin        cellOutline.draw(canvas);
285666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
286666ea1b28a76aeba74744148b15099254d918671Owen Lin
287666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void moveDataWindow() {
288666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Calculate visible region according to scroll position.
289666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startRow = (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
290666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endRow = (mScrollY + getHeight() - mSpec.mCellSpacing - 1)
291666ea1b28a76aeba74744148b15099254d918671Owen Lin                / mBlockHeight + 1;
292666ea1b28a76aeba74744148b15099254d918671Owen Lin
293666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Limit startRow and endRow to the valid range.
294666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Make sure we handle the mRows == 0 case right.
295666ea1b28a76aeba74744148b15099254d918671Owen Lin        startRow = Math.max(Math.min(startRow, mRows - 1), 0);
296666ea1b28a76aeba74744148b15099254d918671Owen Lin        endRow = Math.max(Math.min(endRow, mRows), 0);
297666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager.setVisibleRows(startRow, endRow);
298666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
299666ea1b28a76aeba74744148b15099254d918671Owen Lin
300666ea1b28a76aeba74744148b15099254d918671Owen Lin    // In MyGestureDetector we have to check canHandleEvent() because
301666ea1b28a76aeba74744148b15099254d918671Owen Lin    // GestureDetector could queue events and fire them later. At that time
302666ea1b28a76aeba74744148b15099254d918671Owen Lin    // stop() may have already been called and we can't handle the events.
303666ea1b28a76aeba74744148b15099254d918671Owen Lin    private class MyGestureDetector extends SimpleOnGestureListener {
304666ea1b28a76aeba74744148b15099254d918671Owen Lin        private AudioManager mAudioManager;
305666ea1b28a76aeba74744148b15099254d918671Owen Lin
306666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
307666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onDown(MotionEvent e) {
308666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
309666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mScroller != null && !mScroller.isFinished()) {
310666ea1b28a76aeba74744148b15099254d918671Owen Lin                mScroller.forceFinished(true);
311666ea1b28a76aeba74744148b15099254d918671Owen Lin                return false;
312666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
313666ea1b28a76aeba74744148b15099254d918671Owen Lin            int index = computeSelectedIndex(e.getX(), e.getY());
314666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= 0 && index < mCount) {
315666ea1b28a76aeba74744148b15099254d918671Owen Lin                setSelectedIndex(index);
316666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
317666ea1b28a76aeba74744148b15099254d918671Owen Lin                setSelectedIndex(INDEX_NONE);
318666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
319666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
320666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
321666ea1b28a76aeba74744148b15099254d918671Owen Lin
322666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
323666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onFling(MotionEvent e1, MotionEvent e2,
324666ea1b28a76aeba74744148b15099254d918671Owen Lin                float velocityX, float velocityY) {
325666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
326666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (velocityY > MAX_FLING_VELOCITY) {
327666ea1b28a76aeba74744148b15099254d918671Owen Lin                velocityY = MAX_FLING_VELOCITY;
328666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else if (velocityY < -MAX_FLING_VELOCITY) {
329666ea1b28a76aeba74744148b15099254d918671Owen Lin                velocityY = -MAX_FLING_VELOCITY;
330666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
331666ea1b28a76aeba74744148b15099254d918671Owen Lin
332666ea1b28a76aeba74744148b15099254d918671Owen Lin            setSelectedIndex(INDEX_NONE);
333666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
334666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.fling(0, mScrollY, 0, -(int) velocityY, 0, 0, 0,
335666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mMaxScrollY);
336666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
337666ea1b28a76aeba74744148b15099254d918671Owen Lin
338666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
339666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
340666ea1b28a76aeba74744148b15099254d918671Owen Lin
341666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
342666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void onLongPress(MotionEvent e) {
343666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return;
344666ea1b28a76aeba74744148b15099254d918671Owen Lin            performLongClick();
345666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
346666ea1b28a76aeba74744148b15099254d918671Owen Lin
347666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
348666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onScroll(MotionEvent e1, MotionEvent e2,
349666ea1b28a76aeba74744148b15099254d918671Owen Lin                                float distanceX, float distanceY) {
350666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
351666ea1b28a76aeba74744148b15099254d918671Owen Lin            setSelectedIndex(INDEX_NONE);
352666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollBy(0, (int) distanceY);
353666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidate();
354666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
355666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
356666ea1b28a76aeba74744148b15099254d918671Owen Lin
357666ea1b28a76aeba74744148b15099254d918671Owen Lin        @Override
358666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean onSingleTapConfirmed(MotionEvent e) {
359666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (!canHandleEvent()) return false;
360666ea1b28a76aeba74744148b15099254d918671Owen Lin            int index = computeSelectedIndex(e.getX(), e.getY());
361666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= 0 && index < mCount) {
362666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Play click sound.
363666ea1b28a76aeba74744148b15099254d918671Owen Lin                if (mAudioManager == null) {
364666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mAudioManager = (AudioManager) getContext()
365666ea1b28a76aeba74744148b15099254d918671Owen Lin                            .getSystemService(Context.AUDIO_SERVICE);
366666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
367666ea1b28a76aeba74744148b15099254d918671Owen Lin                mAudioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
368666ea1b28a76aeba74744148b15099254d918671Owen Lin
369666ea1b28a76aeba74744148b15099254d918671Owen Lin                mListener.onImageTapped(index);
370666ea1b28a76aeba74744148b15099254d918671Owen Lin                return true;
371666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
372666ea1b28a76aeba74744148b15099254d918671Owen Lin            return false;
373666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
374666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
375666ea1b28a76aeba74744148b15099254d918671Owen Lin
376666ea1b28a76aeba74744148b15099254d918671Owen Lin    public int getCurrentSelection() {
377666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mCurrentSelection;
378666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
379666ea1b28a76aeba74744148b15099254d918671Owen Lin
380666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void invalidateImage(int index) {
381666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (index != INDEX_NONE) {
382666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.invalidateImage(index);
383666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
384666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
385666ea1b28a76aeba74744148b15099254d918671Owen Lin
386666ea1b28a76aeba74744148b15099254d918671Owen Lin    /**
387666ea1b28a76aeba74744148b15099254d918671Owen Lin     *
388666ea1b28a76aeba74744148b15099254d918671Owen Lin     * @param index <code>INDEX_NONE</code> (-1) means remove selection.
389666ea1b28a76aeba74744148b15099254d918671Owen Lin     */
390666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setSelectedIndex(int index) {
391666ea1b28a76aeba74744148b15099254d918671Owen Lin        // A selection box will be shown for the image that being selected,
392666ea1b28a76aeba74744148b15099254d918671Owen Lin        // (by finger or by the dpad center key). The selection box can be drawn
393666ea1b28a76aeba74744148b15099254d918671Owen Lin        // in two colors. One color (yellow) is used when the the image is
394666ea1b28a76aeba74744148b15099254d918671Owen Lin        // still being tapped or clicked (the finger is still on the touch
395666ea1b28a76aeba74744148b15099254d918671Owen Lin        // screen or the dpad center key is not released). Another color
396666ea1b28a76aeba74744148b15099254d918671Owen Lin        // (orange) is used after the finger leaves touch screen or the dpad
397666ea1b28a76aeba74744148b15099254d918671Owen Lin        // center key is released.
398666ea1b28a76aeba74744148b15099254d918671Owen Lin
399666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection == index) {
400666ea1b28a76aeba74744148b15099254d918671Owen Lin            return;
401666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
402666ea1b28a76aeba74744148b15099254d918671Owen Lin        // This happens when the last picture is deleted.
403666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCurrentSelection = Math.min(index, mCount - 1);
404666ea1b28a76aeba74744148b15099254d918671Owen Lin
405666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection != INDEX_NONE) {
406666ea1b28a76aeba74744148b15099254d918671Owen Lin            ensureVisible(mCurrentSelection);
407666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
408666ea1b28a76aeba74744148b15099254d918671Owen Lin        invalidate();
409666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
410666ea1b28a76aeba74744148b15099254d918671Owen Lin
411666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollToImage(int index) {
412666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(index);
413666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(0, r.top);
414666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
415666ea1b28a76aeba74744148b15099254d918671Owen Lin
416666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollToVisible(int index) {
417666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(index);
418666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = getScrollY();
419666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bottom = getScrollY() + getHeight();
420666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (r.bottom > bottom) {
421666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, r.bottom - getHeight());
422666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else if (r.top < top) {
423666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, r.top);
424666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
425666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
426666ea1b28a76aeba74744148b15099254d918671Owen Lin
427666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void ensureVisible(int pos) {
428666ea1b28a76aeba74744148b15099254d918671Owen Lin        Rect r = getRectForPosition(pos);
429666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = getScrollY();
430666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bot = top + getHeight();
431666ea1b28a76aeba74744148b15099254d918671Owen Lin
432666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (r.bottom > bot) {
433666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
434666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.startScroll(mScrollX, mScrollY, 0,
435666ea1b28a76aeba74744148b15099254d918671Owen Lin                    r.bottom - getHeight() - mScrollY, 200);
436666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
437666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else if (r.top < top) {
438666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller = new Scroller(getContext());
439666ea1b28a76aeba74744148b15099254d918671Owen Lin            mScroller.startScroll(mScrollX, mScrollY, 0, r.top - mScrollY, 200);
440666ea1b28a76aeba74744148b15099254d918671Owen Lin            computeScroll();
441666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
442666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
443666ea1b28a76aeba74744148b15099254d918671Owen Lin
444666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void start() {
445666ea1b28a76aeba74744148b15099254d918671Owen Lin        // These must be set before start().
446666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mLoader != null);
447666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mListener != null);
448666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(mDrawAdapter != null);
449666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRunning = true;
450666ea1b28a76aeba74744148b15099254d918671Owen Lin        requestLayout();
451666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
452666ea1b28a76aeba74744148b15099254d918671Owen Lin
453666ea1b28a76aeba74744148b15099254d918671Owen Lin    // If the the underlying data is changed, for example,
454666ea1b28a76aeba74744148b15099254d918671Owen Lin    // an image is deleted, or the size choice is changed,
455666ea1b28a76aeba74744148b15099254d918671Owen Lin    // The following sequence is needed:
456666ea1b28a76aeba74744148b15099254d918671Owen Lin    //
457666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.stop();
458666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.set...(...);
459666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.set...(...);
460666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mGvs.start();
461666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void stop() {
462666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Remove the long press callback from the queue if we are going to
463666ea1b28a76aeba74744148b15099254d918671Owen Lin        // stop.
464666ea1b28a76aeba74744148b15099254d918671Owen Lin        mHandler.removeCallbacks(mLongPressCallback);
465666ea1b28a76aeba74744148b15099254d918671Owen Lin        mScroller = null;
466666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mImageBlockManager != null) {
467666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager.recycle();
468666ea1b28a76aeba74744148b15099254d918671Owen Lin            mImageBlockManager = null;
469666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
470666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRunning = false;
471666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCurrentSelection = INDEX_NONE;
472666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
473666ea1b28a76aeba74744148b15099254d918671Owen Lin
474666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
475666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void onDraw(Canvas canvas) {
476666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.onDraw(canvas);
477666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return;
478666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageBlockManager.doDraw(canvas, getWidth(), getHeight(), mScrollY);
479666ea1b28a76aeba74744148b15099254d918671Owen Lin        paintDecoration(canvas);
480666ea1b28a76aeba74744148b15099254d918671Owen Lin        paintSelection(canvas);
481666ea1b28a76aeba74744148b15099254d918671Owen Lin        moveDataWindow();
482666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
483666ea1b28a76aeba74744148b15099254d918671Owen Lin
484666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
485666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void computeScroll() {
486666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mScroller != null) {
487666ea1b28a76aeba74744148b15099254d918671Owen Lin            boolean more = mScroller.computeScrollOffset();
488666ea1b28a76aeba74744148b15099254d918671Owen Lin            scrollTo(0, mScroller.getCurrY());
489666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (more) {
490666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();  // So we draw again
491666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
492666ea1b28a76aeba74744148b15099254d918671Owen Lin                mScroller = null;
493666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
494666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else {
495666ea1b28a76aeba74744148b15099254d918671Owen Lin            super.computeScroll();
496666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
497666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
498666ea1b28a76aeba74744148b15099254d918671Owen Lin
499666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Return the rectange for the thumbnail in the given position.
500666ea1b28a76aeba74744148b15099254d918671Owen Lin    Rect getRectForPosition(int pos) {
501666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = pos / mColumns;
502666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = pos - (row * mColumns);
503666ea1b28a76aeba74744148b15099254d918671Owen Lin
504666ea1b28a76aeba74744148b15099254d918671Owen Lin        int left = mSpec.mLeftEdgePadding
505666ea1b28a76aeba74744148b15099254d918671Owen Lin                + (col * (mSpec.mCellWidth + mSpec.mCellSpacing));
506666ea1b28a76aeba74744148b15099254d918671Owen Lin        int top = row * mBlockHeight;
507666ea1b28a76aeba74744148b15099254d918671Owen Lin
508666ea1b28a76aeba74744148b15099254d918671Owen Lin        return new Rect(left, top,
509666ea1b28a76aeba74744148b15099254d918671Owen Lin                left + mSpec.mCellWidth + mSpec.mCellSpacing,
510666ea1b28a76aeba74744148b15099254d918671Owen Lin                top + mSpec.mCellHeight + mSpec.mCellSpacing);
511666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
512666ea1b28a76aeba74744148b15099254d918671Owen Lin
513666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Inverse of getRectForPosition: from screen coordinate to image position.
514666ea1b28a76aeba74744148b15099254d918671Owen Lin    int computeSelectedIndex(float xFloat, float yFloat) {
515666ea1b28a76aeba74744148b15099254d918671Owen Lin        int x = (int) xFloat;
516666ea1b28a76aeba74744148b15099254d918671Owen Lin        int y = (int) yFloat;
517666ea1b28a76aeba74744148b15099254d918671Owen Lin
518666ea1b28a76aeba74744148b15099254d918671Owen Lin        int spacing = mSpec.mCellSpacing;
519666ea1b28a76aeba74744148b15099254d918671Owen Lin        int leftSpacing = mSpec.mLeftEdgePadding;
520666ea1b28a76aeba74744148b15099254d918671Owen Lin
521666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = (mScrollY + y - spacing) / (mSpec.mCellHeight + spacing);
522666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = Math.min(mColumns - 1,
523666ea1b28a76aeba74744148b15099254d918671Owen Lin                (x - leftSpacing) / (mSpec.mCellWidth + spacing));
524666ea1b28a76aeba74744148b15099254d918671Owen Lin        return (row * mColumns) + col;
525666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
526666ea1b28a76aeba74744148b15099254d918671Owen Lin
527666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
528666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onTouchEvent(MotionEvent ev) {
529666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) {
530666ea1b28a76aeba74744148b15099254d918671Owen Lin            return false;
531666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
532666ea1b28a76aeba74744148b15099254d918671Owen Lin        switch (ev.getAction()) {
533666ea1b28a76aeba74744148b15099254d918671Owen Lin            case MotionEvent.ACTION_DOWN:
534666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCurrentPressState |= TAPPING_FLAG;
535666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();
536666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
537666ea1b28a76aeba74744148b15099254d918671Owen Lin            case MotionEvent.ACTION_UP:
538666ea1b28a76aeba74744148b15099254d918671Owen Lin                mCurrentPressState &= ~TAPPING_FLAG;
539666ea1b28a76aeba74744148b15099254d918671Owen Lin                invalidate();
540666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
541666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
542666ea1b28a76aeba74744148b15099254d918671Owen Lin        mGestureDetector.onTouchEvent(ev);
543666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Consume all events
544666ea1b28a76aeba74744148b15099254d918671Owen Lin        return true;
545666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
546666ea1b28a76aeba74744148b15099254d918671Owen Lin
547666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
548666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollBy(int x, int y) {
549666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(mScrollX + x, mScrollY + y);
550666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
551666ea1b28a76aeba74744148b15099254d918671Owen Lin
552666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollTo(float scrollPosition) {
553666ea1b28a76aeba74744148b15099254d918671Owen Lin        scrollTo(0, Math.round(scrollPosition * mMaxScrollY));
554666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
555666ea1b28a76aeba74744148b15099254d918671Owen Lin
556666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
557666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void scrollTo(int x, int y) {
558666ea1b28a76aeba74744148b15099254d918671Owen Lin        y = Math.max(0, Math.min(mMaxScrollY, y));
559666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mSpec != null) {
560666ea1b28a76aeba74744148b15099254d918671Owen Lin            mListener.onScroll((float) mScrollY / mMaxScrollY);
561666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
562666ea1b28a76aeba74744148b15099254d918671Owen Lin        super.scrollTo(x, y);
563666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
564666ea1b28a76aeba74744148b15099254d918671Owen Lin
565666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean canHandleEvent() {
566666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mRunning && mLayoutComplete;
567666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
568666ea1b28a76aeba74744148b15099254d918671Owen Lin
569666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mLongPressCallback = new Runnable() {
570666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void run() {
571666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCurrentPressState &= ~CLICKING_FLAG;
572666ea1b28a76aeba74744148b15099254d918671Owen Lin            showContextMenu();
573666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
574666ea1b28a76aeba74744148b15099254d918671Owen Lin    };
575666ea1b28a76aeba74744148b15099254d918671Owen Lin
576666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
577666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onKeyDown(int keyCode, KeyEvent event) {
578666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return false;
579666ea1b28a76aeba74744148b15099254d918671Owen Lin        int sel = mCurrentSelection;
580666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (sel != INDEX_NONE) {
581666ea1b28a76aeba74744148b15099254d918671Owen Lin            switch (keyCode) {
582666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_RIGHT:
583666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel != mCount - 1 && (sel % mColumns < mColumns - 1)) {
584666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel += 1;
585666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
586666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
587666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_LEFT:
588666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel > 0 && (sel % mColumns != 0)) {
589666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel -= 1;
590666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
591666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
592666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_UP:
593666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (sel >= mColumns) {
594666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel -= mColumns;
595666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
596666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
597666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_DOWN:
598666ea1b28a76aeba74744148b15099254d918671Owen Lin                    sel = Math.min(mCount - 1, sel + mColumns);
599666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
600666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_CENTER:
601666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (event.getRepeatCount() == 0) {
602666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mCurrentPressState |= CLICKING_FLAG;
603666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mHandler.postDelayed(mLongPressCallback,
604666ea1b28a76aeba74744148b15099254d918671Owen Lin                                ViewConfiguration.getLongPressTimeout());
605666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
606666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
607666ea1b28a76aeba74744148b15099254d918671Owen Lin                default:
608666ea1b28a76aeba74744148b15099254d918671Owen Lin                    return super.onKeyDown(keyCode, event);
609666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
610666ea1b28a76aeba74744148b15099254d918671Owen Lin        } else {
611666ea1b28a76aeba74744148b15099254d918671Owen Lin            switch (keyCode) {
612666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_RIGHT:
613666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_LEFT:
614666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_UP:
615666ea1b28a76aeba74744148b15099254d918671Owen Lin                case KeyEvent.KEYCODE_DPAD_DOWN:
616666ea1b28a76aeba74744148b15099254d918671Owen Lin                        int startRow =
617666ea1b28a76aeba74744148b15099254d918671Owen Lin                                (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
618666ea1b28a76aeba74744148b15099254d918671Owen Lin                        int topPos = startRow * mColumns;
619666ea1b28a76aeba74744148b15099254d918671Owen Lin                        Rect r = getRectForPosition(topPos);
620666ea1b28a76aeba74744148b15099254d918671Owen Lin                        if (r.top < getScrollY()) {
621666ea1b28a76aeba74744148b15099254d918671Owen Lin                            topPos += mColumns;
622666ea1b28a76aeba74744148b15099254d918671Owen Lin                        }
623666ea1b28a76aeba74744148b15099254d918671Owen Lin                        topPos = Math.min(mCount - 1, topPos);
624666ea1b28a76aeba74744148b15099254d918671Owen Lin                        sel = topPos;
625666ea1b28a76aeba74744148b15099254d918671Owen Lin                    break;
626666ea1b28a76aeba74744148b15099254d918671Owen Lin                default:
627666ea1b28a76aeba74744148b15099254d918671Owen Lin                    return super.onKeyDown(keyCode, event);
628666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
629666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
630666ea1b28a76aeba74744148b15099254d918671Owen Lin        setSelectedIndex(sel);
631666ea1b28a76aeba74744148b15099254d918671Owen Lin        return true;
632666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
633666ea1b28a76aeba74744148b15099254d918671Owen Lin
634666ea1b28a76aeba74744148b15099254d918671Owen Lin    @Override
635666ea1b28a76aeba74744148b15099254d918671Owen Lin    public boolean onKeyUp(int keyCode, KeyEvent event) {
636666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!canHandleEvent()) return false;
637666ea1b28a76aeba74744148b15099254d918671Owen Lin
638666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
639666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCurrentPressState &= ~CLICKING_FLAG;
640666ea1b28a76aeba74744148b15099254d918671Owen Lin            invalidate();
641666ea1b28a76aeba74744148b15099254d918671Owen Lin
642666ea1b28a76aeba74744148b15099254d918671Owen Lin            // The keyUp doesn't get called when the longpress menu comes up. We
643666ea1b28a76aeba74744148b15099254d918671Owen Lin            // only get here when the user lets go of the center key before the
644666ea1b28a76aeba74744148b15099254d918671Owen Lin            // longpress menu comes up.
645666ea1b28a76aeba74744148b15099254d918671Owen Lin            mHandler.removeCallbacks(mLongPressCallback);
646666ea1b28a76aeba74744148b15099254d918671Owen Lin
647666ea1b28a76aeba74744148b15099254d918671Owen Lin            // open the photo
648666ea1b28a76aeba74744148b15099254d918671Owen Lin            mListener.onImageClicked(mCurrentSelection);
649666ea1b28a76aeba74744148b15099254d918671Owen Lin            return true;
650666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
651666ea1b28a76aeba74744148b15099254d918671Owen Lin        return super.onKeyUp(keyCode, event);
652666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
653666ea1b28a76aeba74744148b15099254d918671Owen Lin
654666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void paintDecoration(Canvas canvas) {
655666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (!mDrawAdapter.needsDecoration()) return;
656666ea1b28a76aeba74744148b15099254d918671Owen Lin
657666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Calculate visible region according to scroll position.
658666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startRow = (mScrollY - mSpec.mCellSpacing) / mBlockHeight;
659666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endRow = (mScrollY + getHeight() - mSpec.mCellSpacing - 1)
660666ea1b28a76aeba74744148b15099254d918671Owen Lin                / mBlockHeight + 1;
661666ea1b28a76aeba74744148b15099254d918671Owen Lin
662666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Limit startRow and endRow to the valid range.
663666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Make sure we handle the mRows == 0 case right.
664666ea1b28a76aeba74744148b15099254d918671Owen Lin        startRow = Math.max(Math.min(startRow, mRows - 1), 0);
665666ea1b28a76aeba74744148b15099254d918671Owen Lin        endRow = Math.max(Math.min(endRow, mRows), 0);
666666ea1b28a76aeba74744148b15099254d918671Owen Lin
667666ea1b28a76aeba74744148b15099254d918671Owen Lin        int startIndex = startRow * mColumns;
668666ea1b28a76aeba74744148b15099254d918671Owen Lin        int endIndex = Math.min(endRow * mColumns, mCount);
669666ea1b28a76aeba74744148b15099254d918671Owen Lin
670666ea1b28a76aeba74744148b15099254d918671Owen Lin        int xPos = mSpec.mLeftEdgePadding;
671666ea1b28a76aeba74744148b15099254d918671Owen Lin        int yPos = mSpec.mCellSpacing + startRow * mBlockHeight;
672666ea1b28a76aeba74744148b15099254d918671Owen Lin        int off = 0;
673666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = startIndex; i < endIndex; i++) {
674666ea1b28a76aeba74744148b15099254d918671Owen Lin            IImage image = mAllImages.getImageAt(i);
675666ea1b28a76aeba74744148b15099254d918671Owen Lin
676666ea1b28a76aeba74744148b15099254d918671Owen Lin            mDrawAdapter.drawDecoration(canvas, image, xPos, yPos,
677666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mSpec.mCellWidth, mSpec.mCellHeight);
678666ea1b28a76aeba74744148b15099254d918671Owen Lin
679666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Calculate next position
680666ea1b28a76aeba74744148b15099254d918671Owen Lin            off += 1;
681666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (off == mColumns) {
682666ea1b28a76aeba74744148b15099254d918671Owen Lin                xPos = mSpec.mLeftEdgePadding;
683666ea1b28a76aeba74744148b15099254d918671Owen Lin                yPos += mBlockHeight;
684666ea1b28a76aeba74744148b15099254d918671Owen Lin                off = 0;
685666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
686666ea1b28a76aeba74744148b15099254d918671Owen Lin                xPos += mSpec.mCellWidth + mSpec.mCellSpacing;
687666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
688666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
689666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
690666ea1b28a76aeba74744148b15099254d918671Owen Lin
691666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void paintSelection(Canvas canvas) {
692666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentSelection == INDEX_NONE) return;
693666ea1b28a76aeba74744148b15099254d918671Owen Lin
694666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = mCurrentSelection / mColumns;
695666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = mCurrentSelection - (row * mColumns);
696666ea1b28a76aeba74744148b15099254d918671Owen Lin
697666ea1b28a76aeba74744148b15099254d918671Owen Lin        int spacing = mSpec.mCellSpacing;
698666ea1b28a76aeba74744148b15099254d918671Owen Lin        int leftSpacing = mSpec.mLeftEdgePadding;
699666ea1b28a76aeba74744148b15099254d918671Owen Lin        int xPos = leftSpacing + (col * (mSpec.mCellWidth + spacing));
700666ea1b28a76aeba74744148b15099254d918671Owen Lin        int yTop = spacing + (row * mBlockHeight);
701666ea1b28a76aeba74744148b15099254d918671Owen Lin
702666ea1b28a76aeba74744148b15099254d918671Owen Lin        int type = OUTLINE_SELECTED;
703666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCurrentPressState != 0) {
704666ea1b28a76aeba74744148b15099254d918671Owen Lin            type = OUTLINE_PRESSED;
705666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
706666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawBitmap(mOutline[type], xPos, yTop, null);
707666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
708666ea1b28a76aeba74744148b15099254d918671Owen Lin}
709666ea1b28a76aeba74744148b15099254d918671Owen Lin
710666ea1b28a76aeba74744148b15099254d918671Owen Linclass ImageBlockManager {
711666ea1b28a76aeba74744148b15099254d918671Owen Lin    @SuppressWarnings("unused")
712666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final String TAG = "ImageBlockManager";
713666ea1b28a76aeba74744148b15099254d918671Owen Lin
714666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Number of rows we want to cache.
715666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Assume there are 6 rows per page, this caches 5 pages.
716666ea1b28a76aeba74744148b15099254d918671Owen Lin    private static final int CACHE_ROWS = 30;
717666ea1b28a76aeba74744148b15099254d918671Owen Lin
718666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mCache maps from row number to the ImageBlock.
719666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final HashMap<Integer, ImageBlock> mCache;
720666ea1b28a76aeba74744148b15099254d918671Owen Lin
721666ea1b28a76aeba74744148b15099254d918671Owen Lin    // These are parameters set in the constructor.
722666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Handler mHandler;
723666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Runnable mRedrawCallback;  // Called after a row is loaded,
724666ea1b28a76aeba74744148b15099254d918671Owen Lin                                             // so GridViewSpecial can draw
725666ea1b28a76aeba74744148b15099254d918671Owen Lin                                             // again using the new images.
726666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final IImageList mImageList;
727666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final ImageLoader mLoader;
728666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final GridViewSpecial.DrawAdapter mDrawAdapter;
729666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final GridViewSpecial.LayoutSpec mSpec;
730666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mColumns;  // Columns per row.
731666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mBlockWidth;  // The width of an ImageBlock.
732666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final Bitmap mOutline;  // The outline bitmap put on top of each
733666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    // image.
734666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mCount;  // Cache mImageList.getCount().
735666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mRows;  // Cache (mCount + mColumns - 1) / mColumns
736666ea1b28a76aeba74744148b15099254d918671Owen Lin    private final int mBlockHeight;  // The height of an ImageBlock.
737666ea1b28a76aeba74744148b15099254d918671Owen Lin
738666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Visible row range: [mStartRow, mEndRow). Set by setVisibleRows().
739666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mStartRow = 0;
740666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int mEndRow = 0;
741666ea1b28a76aeba74744148b15099254d918671Owen Lin
742666ea1b28a76aeba74744148b15099254d918671Owen Lin    ImageBlockManager(Handler handler, Runnable redrawCallback,
743666ea1b28a76aeba74744148b15099254d918671Owen Lin            IImageList imageList, ImageLoader loader,
744666ea1b28a76aeba74744148b15099254d918671Owen Lin            GridViewSpecial.DrawAdapter adapter,
745666ea1b28a76aeba74744148b15099254d918671Owen Lin            GridViewSpecial.LayoutSpec spec,
746666ea1b28a76aeba74744148b15099254d918671Owen Lin            int columns, int blockWidth, Bitmap outline) {
747666ea1b28a76aeba74744148b15099254d918671Owen Lin        mHandler = handler;
748666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRedrawCallback = redrawCallback;
749666ea1b28a76aeba74744148b15099254d918671Owen Lin        mImageList = imageList;
750666ea1b28a76aeba74744148b15099254d918671Owen Lin        mLoader = loader;
751666ea1b28a76aeba74744148b15099254d918671Owen Lin        mDrawAdapter = adapter;
752666ea1b28a76aeba74744148b15099254d918671Owen Lin        mSpec = spec;
753666ea1b28a76aeba74744148b15099254d918671Owen Lin        mColumns = columns;
754666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockWidth = blockWidth;
755666ea1b28a76aeba74744148b15099254d918671Owen Lin        mOutline = outline;
756666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBlockHeight = mSpec.mCellSpacing + mSpec.mCellHeight;
757666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCount = imageList.getCount();
758666ea1b28a76aeba74744148b15099254d918671Owen Lin        mRows = (mCount + mColumns - 1) / mColumns;
759666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache = new HashMap<Integer, ImageBlock>();
760666ea1b28a76aeba74744148b15099254d918671Owen Lin        mPendingRequest = 0;
761666ea1b28a76aeba74744148b15099254d918671Owen Lin        initGraphics();
762666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
763666ea1b28a76aeba74744148b15099254d918671Owen Lin
764666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Set the window of visible rows. Once set we will start to load them as
765666ea1b28a76aeba74744148b15099254d918671Owen Lin    // soon as possible (if they are not already in cache).
766666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void setVisibleRows(int startRow, int endRow) {
767666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (startRow != mStartRow || endRow != mEndRow) {
768666ea1b28a76aeba74744148b15099254d918671Owen Lin            mStartRow = startRow;
769666ea1b28a76aeba74744148b15099254d918671Owen Lin            mEndRow = endRow;
770666ea1b28a76aeba74744148b15099254d918671Owen Lin            startLoading();
771666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
772666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
773666ea1b28a76aeba74744148b15099254d918671Owen Lin
774666ea1b28a76aeba74744148b15099254d918671Owen Lin    int mPendingRequest;  // Number of pending requests (sent to ImageLoader).
775666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We want to keep enough requests in ImageLoader's queue, but not too
776666ea1b28a76aeba74744148b15099254d918671Owen Lin    // many.
777666ea1b28a76aeba74744148b15099254d918671Owen Lin    static final int REQUESTS_LOW = 3;
778666ea1b28a76aeba74744148b15099254d918671Owen Lin    static final int REQUESTS_HIGH = 6;
779666ea1b28a76aeba74744148b15099254d918671Owen Lin
780666ea1b28a76aeba74744148b15099254d918671Owen Lin    // After clear requests currently in queue, start loading the thumbnails.
781666ea1b28a76aeba74744148b15099254d918671Owen Lin    // We need to clear the queue first because the proper order of loading
782666ea1b28a76aeba74744148b15099254d918671Owen Lin    // may have changed (because the visible region changed, or some images
783666ea1b28a76aeba74744148b15099254d918671Owen Lin    // have been invalidated).
784666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void startLoading() {
785666ea1b28a76aeba74744148b15099254d918671Owen Lin        clearLoaderQueue();
786666ea1b28a76aeba74744148b15099254d918671Owen Lin        continueLoading();
787666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
788666ea1b28a76aeba74744148b15099254d918671Owen Lin
789666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void clearLoaderQueue() {
790666ea1b28a76aeba74744148b15099254d918671Owen Lin        int[] tags = mLoader.clearQueue();
791666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int pos : tags) {
792666ea1b28a76aeba74744148b15099254d918671Owen Lin            int row = pos / mColumns;
793666ea1b28a76aeba74744148b15099254d918671Owen Lin            int col = pos - row * mColumns;
794666ea1b28a76aeba74744148b15099254d918671Owen Lin            ImageBlock blk = mCache.get(row);
795666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert(blk != null);  // We won't reuse the block if it has pending
796666ea1b28a76aeba74744148b15099254d918671Owen Lin                                  // requests. See getEmptyBlock().
797666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.cancelRequest(col);
798666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
799666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
800666ea1b28a76aeba74744148b15099254d918671Owen Lin
801666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Scan the cache and send requests to ImageLoader if needed.
802666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void continueLoading() {
803666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Check if we still have enough requests in the queue.
804666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mPendingRequest >= REQUESTS_LOW) return;
805666ea1b28a76aeba74744148b15099254d918671Owen Lin
806666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Scan the visible rows.
807666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = mStartRow; i < mEndRow; i++) {
808666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (scanOne(i)) return;
809666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
810666ea1b28a76aeba74744148b15099254d918671Owen Lin
811666ea1b28a76aeba74744148b15099254d918671Owen Lin        int range = (CACHE_ROWS - (mEndRow - mStartRow)) / 2;
812666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Scan other rows.
813666ea1b28a76aeba74744148b15099254d918671Owen Lin        // d is the distance between the row and visible region.
814666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int d = 1; d <= range; d++) {
815666ea1b28a76aeba74744148b15099254d918671Owen Lin            int after = mEndRow - 1 + d;
816666ea1b28a76aeba74744148b15099254d918671Owen Lin            int before = mStartRow - d;
817666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (after >= mRows && before < 0) {
818666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;  // Nothing more the scan.
819666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
820666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (after < mRows && scanOne(after)) return;
821666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (before >= 0 && scanOne(before)) return;
822666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
823666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
824666ea1b28a76aeba74744148b15099254d918671Owen Lin
825666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Returns true if we can stop scanning.
826666ea1b28a76aeba74744148b15099254d918671Owen Lin    private boolean scanOne(int i) {
827666ea1b28a76aeba74744148b15099254d918671Owen Lin        mPendingRequest += tryToLoad(i);
828666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mPendingRequest >= REQUESTS_HIGH;
829666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
830666ea1b28a76aeba74744148b15099254d918671Owen Lin
831666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Returns number of requests we issued for this row.
832666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int tryToLoad(int row) {
833666ea1b28a76aeba74744148b15099254d918671Owen Lin        Assert(row >= 0 && row < mRows);
834666ea1b28a76aeba74744148b15099254d918671Owen Lin        ImageBlock blk = mCache.get(row);
835666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (blk == null) {
836666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Find an empty block
837666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk = getEmptyBlock();
838666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.setRow(row);
839666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.invalidate();
840666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCache.put(row, blk);
841666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
842666ea1b28a76aeba74744148b15099254d918671Owen Lin        return blk.loadImages();
843666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
844666ea1b28a76aeba74744148b15099254d918671Owen Lin
845666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Get an empty block for the cache.
846666ea1b28a76aeba74744148b15099254d918671Owen Lin    private ImageBlock getEmptyBlock() {
847666ea1b28a76aeba74744148b15099254d918671Owen Lin        // See if we can allocate a new block.
848666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (mCache.size() < CACHE_ROWS) {
849666ea1b28a76aeba74744148b15099254d918671Owen Lin            return new ImageBlock();
850666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
851666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Reclaim the old block with largest distance from the visible region.
852666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bestDistance = -1;
853666ea1b28a76aeba74744148b15099254d918671Owen Lin        int bestIndex = -1;
854666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int index : mCache.keySet()) {
855666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Make sure we don't reclaim a block which still has pending
856666ea1b28a76aeba74744148b15099254d918671Owen Lin            // request.
857666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mCache.get(index).hasPendingRequests()) {
858666ea1b28a76aeba74744148b15099254d918671Owen Lin                continue;
859666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
860666ea1b28a76aeba74744148b15099254d918671Owen Lin            int dist = 0;
861666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (index >= mEndRow) {
862666ea1b28a76aeba74744148b15099254d918671Owen Lin                dist = index - mEndRow + 1;
863666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else if (index < mStartRow) {
864666ea1b28a76aeba74744148b15099254d918671Owen Lin                dist = mStartRow - index;
865666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
866666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Inside the visible region.
867666ea1b28a76aeba74744148b15099254d918671Owen Lin                continue;
868666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
869666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (dist > bestDistance) {
870666ea1b28a76aeba74744148b15099254d918671Owen Lin                bestDistance = dist;
871666ea1b28a76aeba74744148b15099254d918671Owen Lin                bestIndex = index;
872666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
873666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
874666ea1b28a76aeba74744148b15099254d918671Owen Lin        return mCache.remove(bestIndex);
875666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
876666ea1b28a76aeba74744148b15099254d918671Owen Lin
877666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void invalidateImage(int index) {
878666ea1b28a76aeba74744148b15099254d918671Owen Lin        int row = index / mColumns;
879666ea1b28a76aeba74744148b15099254d918671Owen Lin        int col = index - (row * mColumns);
880666ea1b28a76aeba74744148b15099254d918671Owen Lin        ImageBlock blk = mCache.get(row);
881666ea1b28a76aeba74744148b15099254d918671Owen Lin        if (blk == null) return;
882666ea1b28a76aeba74744148b15099254d918671Owen Lin        if ((blk.mCompletedMask & (1 << col)) != 0) {
883666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.mCompletedMask &= ~(1 << col);
884666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
885666ea1b28a76aeba74744148b15099254d918671Owen Lin        startLoading();
886666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
887666ea1b28a76aeba74744148b15099254d918671Owen Lin
888666ea1b28a76aeba74744148b15099254d918671Owen Lin    // After calling recycle(), the instance should not be used anymore.
889666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void recycle() {
890666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (ImageBlock blk : mCache.values()) {
891666ea1b28a76aeba74744148b15099254d918671Owen Lin            blk.recycle();
892666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
893666ea1b28a76aeba74744148b15099254d918671Owen Lin        mCache.clear();
894666ea1b28a76aeba74744148b15099254d918671Owen Lin        mEmptyBitmap.recycle();
895666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
896666ea1b28a76aeba74744148b15099254d918671Owen Lin
897666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Draw the images to the given canvas.
898666ea1b28a76aeba74744148b15099254d918671Owen Lin    public void doDraw(Canvas canvas, int thisWidth, int thisHeight,
899666ea1b28a76aeba74744148b15099254d918671Owen Lin            int scrollPos) {
900666ea1b28a76aeba74744148b15099254d918671Owen Lin        final int height = mBlockHeight;
901666ea1b28a76aeba74744148b15099254d918671Owen Lin
902666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Note that currentBlock could be negative.
903666ea1b28a76aeba74744148b15099254d918671Owen Lin        int currentBlock = (scrollPos < 0)
904666ea1b28a76aeba74744148b15099254d918671Owen Lin                ? ((scrollPos - height + 1) / height)
905666ea1b28a76aeba74744148b15099254d918671Owen Lin                : (scrollPos / height);
906666ea1b28a76aeba74744148b15099254d918671Owen Lin
907666ea1b28a76aeba74744148b15099254d918671Owen Lin        while (true) {
908666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int yPos = currentBlock * height;
909666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (yPos >= scrollPos + thisHeight) {
910666ea1b28a76aeba74744148b15099254d918671Owen Lin                break;
911666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
912666ea1b28a76aeba74744148b15099254d918671Owen Lin
913666ea1b28a76aeba74744148b15099254d918671Owen Lin            ImageBlock blk = mCache.get(currentBlock);
914666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (blk != null) {
915666ea1b28a76aeba74744148b15099254d918671Owen Lin                blk.doDraw(canvas, 0, yPos);
916666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
917666ea1b28a76aeba74744148b15099254d918671Owen Lin                drawEmptyBlock(canvas, 0, yPos, currentBlock);
918666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
919666ea1b28a76aeba74744148b15099254d918671Owen Lin
920666ea1b28a76aeba74744148b15099254d918671Owen Lin            currentBlock += 1;
921666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
922666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
923666ea1b28a76aeba74744148b15099254d918671Owen Lin
924666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Return number of columns in the given row. (This could be less than
925666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mColumns for the last row).
926666ea1b28a76aeba74744148b15099254d918671Owen Lin    private int numColumns(int row) {
927666ea1b28a76aeba74744148b15099254d918671Owen Lin        return Math.min(mColumns, mCount - row * mColumns);
928666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
929666ea1b28a76aeba74744148b15099254d918671Owen Lin
930666ea1b28a76aeba74744148b15099254d918671Owen Lin    // Draw a block which has not been loaded.
931666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void drawEmptyBlock(Canvas canvas, int xPos, int yPos, int row) {
932666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the background.
933666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawRect(xPos, yPos, xPos + mBlockWidth, yPos + mBlockHeight,
934666ea1b28a76aeba74744148b15099254d918671Owen Lin                mBackgroundPaint);
935666ea1b28a76aeba74744148b15099254d918671Owen Lin
936666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the empty images.
937666ea1b28a76aeba74744148b15099254d918671Owen Lin        int x = xPos + mSpec.mLeftEdgePadding;
938666ea1b28a76aeba74744148b15099254d918671Owen Lin        int y = yPos + mSpec.mCellSpacing;
939666ea1b28a76aeba74744148b15099254d918671Owen Lin        int cols = numColumns(row);
940666ea1b28a76aeba74744148b15099254d918671Owen Lin
941666ea1b28a76aeba74744148b15099254d918671Owen Lin        for (int i = 0; i < cols; i++) {
942666ea1b28a76aeba74744148b15099254d918671Owen Lin            canvas.drawBitmap(mEmptyBitmap, x, y, null);
943666ea1b28a76aeba74744148b15099254d918671Owen Lin            x += (mSpec.mCellWidth + mSpec.mCellSpacing);
944666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
945666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
946666ea1b28a76aeba74744148b15099254d918671Owen Lin
947666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mEmptyBitmap is what we draw if we the wanted block hasn't been loaded.
948666ea1b28a76aeba74744148b15099254d918671Owen Lin    // (If the user scrolls too fast). It is a gray image with normal outline.
949666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mBackgroundPaint is used to draw the (black) background outside
950666ea1b28a76aeba74744148b15099254d918671Owen Lin    // mEmptyBitmap.
951666ea1b28a76aeba74744148b15099254d918671Owen Lin    Paint mBackgroundPaint;
952666ea1b28a76aeba74744148b15099254d918671Owen Lin    private Bitmap mEmptyBitmap;
953666ea1b28a76aeba74744148b15099254d918671Owen Lin
954666ea1b28a76aeba74744148b15099254d918671Owen Lin    private void initGraphics() {
955666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint = new Paint();
956666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint.setStyle(Paint.Style.FILL);
957666ea1b28a76aeba74744148b15099254d918671Owen Lin        mBackgroundPaint.setColor(0xFF000000);  // black
958666ea1b28a76aeba74744148b15099254d918671Owen Lin        mEmptyBitmap = Bitmap.createBitmap(mSpec.mCellWidth, mSpec.mCellHeight,
959666ea1b28a76aeba74744148b15099254d918671Owen Lin                Bitmap.Config.RGB_565);
960666ea1b28a76aeba74744148b15099254d918671Owen Lin        Canvas canvas = new Canvas(mEmptyBitmap);
961666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawRGB(0xDD, 0xDD, 0xDD);
962666ea1b28a76aeba74744148b15099254d918671Owen Lin        canvas.drawBitmap(mOutline, 0, 0, null);
963666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
964666ea1b28a76aeba74744148b15099254d918671Owen Lin
965666ea1b28a76aeba74744148b15099254d918671Owen Lin    // ImageBlock stores bitmap for one row. The loaded thumbnail images are
966666ea1b28a76aeba74744148b15099254d918671Owen Lin    // drawn to mBitmap. mBitmap is later used in onDraw() of GridViewSpecial.
967666ea1b28a76aeba74744148b15099254d918671Owen Lin    private class ImageBlock {
968666ea1b28a76aeba74744148b15099254d918671Owen Lin        private Bitmap mBitmap;
969666ea1b28a76aeba74744148b15099254d918671Owen Lin        private final Canvas mCanvas;
970666ea1b28a76aeba74744148b15099254d918671Owen Lin
971666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Columns which have been requested to the loader
972666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mRequestedMask;
973666ea1b28a76aeba74744148b15099254d918671Owen Lin
974666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Columns which have been completed from the loader
975666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mCompletedMask;
976666ea1b28a76aeba74744148b15099254d918671Owen Lin
977666ea1b28a76aeba74744148b15099254d918671Owen Lin        // The row number this block represents.
978666ea1b28a76aeba74744148b15099254d918671Owen Lin        private int mRow;
979666ea1b28a76aeba74744148b15099254d918671Owen Lin
980666ea1b28a76aeba74744148b15099254d918671Owen Lin        public ImageBlock() {
981666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap = Bitmap.createBitmap(mBlockWidth, mBlockHeight,
982666ea1b28a76aeba74744148b15099254d918671Owen Lin                    Bitmap.Config.RGB_565);
983666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCanvas = new Canvas(mBitmap);
984666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRow = -1;
985666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
986666ea1b28a76aeba74744148b15099254d918671Owen Lin
987666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void setRow(int row) {
988666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRow = row;
989666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
990666ea1b28a76aeba74744148b15099254d918671Owen Lin
991666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void invalidate() {
992666ea1b28a76aeba74744148b15099254d918671Owen Lin            // We do not change mRequestedMask or do cancelAllRequests()
993666ea1b28a76aeba74744148b15099254d918671Owen Lin            // because the data coming from pending requests are valid. (We only
994666ea1b28a76aeba74744148b15099254d918671Owen Lin            // invalidate data which has been drawn to the bitmap).
995666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCompletedMask = 0;
996666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
997666ea1b28a76aeba74744148b15099254d918671Owen Lin
998666ea1b28a76aeba74744148b15099254d918671Owen Lin        // After recycle, the ImageBlock instance should not be accessed.
999666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void recycle() {
1000666ea1b28a76aeba74744148b15099254d918671Owen Lin            cancelAllRequests();
1001666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap.recycle();
1002666ea1b28a76aeba74744148b15099254d918671Owen Lin            mBitmap = null;
1003666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1004666ea1b28a76aeba74744148b15099254d918671Owen Lin
1005666ea1b28a76aeba74744148b15099254d918671Owen Lin        private boolean isVisible() {
1006666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mRow >= mStartRow && mRow < mEndRow;
1007666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1008666ea1b28a76aeba74744148b15099254d918671Owen Lin
1009666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Returns number of requests submitted to ImageLoader.
1010666ea1b28a76aeba74744148b15099254d918671Owen Lin        public int loadImages() {
1011666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert(mRow != -1);
1012666ea1b28a76aeba74744148b15099254d918671Owen Lin
1013666ea1b28a76aeba74744148b15099254d918671Owen Lin            int columns = numColumns(mRow);
1014666ea1b28a76aeba74744148b15099254d918671Owen Lin
1015666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Calculate what we need.
1016666ea1b28a76aeba74744148b15099254d918671Owen Lin            int needMask = ((1 << columns) - 1)
1017666ea1b28a76aeba74744148b15099254d918671Owen Lin                    & ~(mCompletedMask | mRequestedMask);
1018666ea1b28a76aeba74744148b15099254d918671Owen Lin
1019666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (needMask == 0) {
1020666ea1b28a76aeba74744148b15099254d918671Owen Lin                return 0;
1021666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1022666ea1b28a76aeba74744148b15099254d918671Owen Lin
1023666ea1b28a76aeba74744148b15099254d918671Owen Lin            int retVal = 0;
1024666ea1b28a76aeba74744148b15099254d918671Owen Lin            int base = mRow * mColumns;
1025666ea1b28a76aeba74744148b15099254d918671Owen Lin
1026666ea1b28a76aeba74744148b15099254d918671Owen Lin            for (int col = 0; col < columns; col++) {
1027666ea1b28a76aeba74744148b15099254d918671Owen Lin                if ((needMask & (1 << col)) == 0) {
1028666ea1b28a76aeba74744148b15099254d918671Owen Lin                    continue;
1029666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1030666ea1b28a76aeba74744148b15099254d918671Owen Lin
1031666ea1b28a76aeba74744148b15099254d918671Owen Lin                int pos = base + col;
1032666ea1b28a76aeba74744148b15099254d918671Owen Lin
1033666ea1b28a76aeba74744148b15099254d918671Owen Lin                final IImage image = mImageList.getImageAt(pos);
1034666ea1b28a76aeba74744148b15099254d918671Owen Lin                if (image != null) {
1035666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // This callback is passed to ImageLoader. It will invoke
1036666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // loadImageDone() in the main thread. We limit the callback
1037666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // thread to be in this very short function. All other
1038666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // processing is done in the main thread.
1039666ea1b28a76aeba74744148b15099254d918671Owen Lin                    final int colFinal = col;
1040666ea1b28a76aeba74744148b15099254d918671Owen Lin                    ImageLoader.LoadedCallback cb =
1041666ea1b28a76aeba74744148b15099254d918671Owen Lin                            new ImageLoader.LoadedCallback() {
1042666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    public void run(final Bitmap b) {
1043666ea1b28a76aeba74744148b15099254d918671Owen Lin                                        mHandler.post(new Runnable() {
1044666ea1b28a76aeba74744148b15099254d918671Owen Lin                                            public void run() {
1045666ea1b28a76aeba74744148b15099254d918671Owen Lin                                                loadImageDone(image, b,
1046666ea1b28a76aeba74744148b15099254d918671Owen Lin                                                        colFinal);
1047666ea1b28a76aeba74744148b15099254d918671Owen Lin                                            }
1048666ea1b28a76aeba74744148b15099254d918671Owen Lin                                        });
1049666ea1b28a76aeba74744148b15099254d918671Owen Lin                                    }
1050666ea1b28a76aeba74744148b15099254d918671Owen Lin                                };
1051666ea1b28a76aeba74744148b15099254d918671Owen Lin                    // Load Image
1052666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mLoader.getBitmap(image, cb, pos);
1053666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mRequestedMask |= (1 << col);
1054666ea1b28a76aeba74744148b15099254d918671Owen Lin                    retVal += 1;
1055666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1056666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1057666ea1b28a76aeba74744148b15099254d918671Owen Lin
1058666ea1b28a76aeba74744148b15099254d918671Owen Lin            return retVal;
1059666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1060666ea1b28a76aeba74744148b15099254d918671Owen Lin
1061666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Whether this block has pending requests.
1062666ea1b28a76aeba74744148b15099254d918671Owen Lin        public boolean hasPendingRequests() {
1063666ea1b28a76aeba74744148b15099254d918671Owen Lin            return mRequestedMask != 0;
1064666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1065666ea1b28a76aeba74744148b15099254d918671Owen Lin
1066666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Called when an image is loaded.
1067666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void loadImageDone(IImage image, Bitmap b,
1068666ea1b28a76aeba74744148b15099254d918671Owen Lin                int col) {
1069666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (mBitmap == null) return;  // This block has been recycled.
1070666ea1b28a76aeba74744148b15099254d918671Owen Lin
1071666ea1b28a76aeba74744148b15099254d918671Owen Lin            int spacing = mSpec.mCellSpacing;
1072666ea1b28a76aeba74744148b15099254d918671Owen Lin            int leftSpacing = mSpec.mLeftEdgePadding;
1073666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int yPos = spacing;
1074666ea1b28a76aeba74744148b15099254d918671Owen Lin            final int xPos = leftSpacing
1075666ea1b28a76aeba74744148b15099254d918671Owen Lin                    + (col * (mSpec.mCellWidth + spacing));
1076666ea1b28a76aeba74744148b15099254d918671Owen Lin
1077666ea1b28a76aeba74744148b15099254d918671Owen Lin            drawBitmap(image, b, xPos, yPos);
1078666ea1b28a76aeba74744148b15099254d918671Owen Lin
1079666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (b != null) {
1080666ea1b28a76aeba74744148b15099254d918671Owen Lin                b.recycle();
1081666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1082666ea1b28a76aeba74744148b15099254d918671Owen Lin
1083666ea1b28a76aeba74744148b15099254d918671Owen Lin            int mask = (1 << col);
1084666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mCompletedMask & mask) == 0);
1085666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mRequestedMask & mask) != 0);
1086666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRequestedMask &= ~mask;
1087666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCompletedMask |= mask;
1088666ea1b28a76aeba74744148b15099254d918671Owen Lin            mPendingRequest--;
1089666ea1b28a76aeba74744148b15099254d918671Owen Lin
1090666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (isVisible()) {
1091666ea1b28a76aeba74744148b15099254d918671Owen Lin                mRedrawCallback.run();
1092666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1093666ea1b28a76aeba74744148b15099254d918671Owen Lin
1094666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Kick start next block loading.
1095666ea1b28a76aeba74744148b15099254d918671Owen Lin            continueLoading();
1096666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1097666ea1b28a76aeba74744148b15099254d918671Owen Lin
1098666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the loaded bitmap to the block bitmap.
1099666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void drawBitmap(
1100666ea1b28a76aeba74744148b15099254d918671Owen Lin                IImage image, Bitmap b, int xPos, int yPos) {
1101666ea1b28a76aeba74744148b15099254d918671Owen Lin            mDrawAdapter.drawImage(mCanvas, image, b, xPos, yPos,
1102666ea1b28a76aeba74744148b15099254d918671Owen Lin                    mSpec.mCellWidth, mSpec.mCellHeight);
1103666ea1b28a76aeba74744148b15099254d918671Owen Lin            mCanvas.drawBitmap(mOutline, xPos, yPos, null);
1104666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1105666ea1b28a76aeba74744148b15099254d918671Owen Lin
1106666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Draw the block bitmap to the specified canvas.
1107666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void doDraw(Canvas canvas, int xPos, int yPos) {
1108666ea1b28a76aeba74744148b15099254d918671Owen Lin            int cols = numColumns(mRow);
1109666ea1b28a76aeba74744148b15099254d918671Owen Lin
1110666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (cols == mColumns) {
1111666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawBitmap(mBitmap, xPos, yPos, null);
1112666ea1b28a76aeba74744148b15099254d918671Owen Lin            } else {
1113666ea1b28a76aeba74744148b15099254d918671Owen Lin
1114666ea1b28a76aeba74744148b15099254d918671Owen Lin                // This must be the last row -- we draw only part of the block.
1115666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Draw the background.
1116666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawRect(xPos, yPos, xPos + mBlockWidth,
1117666ea1b28a76aeba74744148b15099254d918671Owen Lin                        yPos + mBlockHeight, mBackgroundPaint);
1118666ea1b28a76aeba74744148b15099254d918671Owen Lin                // Draw part of the block.
1119666ea1b28a76aeba74744148b15099254d918671Owen Lin                int w = mSpec.mLeftEdgePadding
1120666ea1b28a76aeba74744148b15099254d918671Owen Lin                        + cols * (mSpec.mCellWidth + mSpec.mCellSpacing);
1121666ea1b28a76aeba74744148b15099254d918671Owen Lin                Rect srcRect = new Rect(0, 0, w, mBlockHeight);
1122666ea1b28a76aeba74744148b15099254d918671Owen Lin                Rect dstRect = new Rect(srcRect);
1123666ea1b28a76aeba74744148b15099254d918671Owen Lin                dstRect.offset(xPos, yPos);
1124666ea1b28a76aeba74744148b15099254d918671Owen Lin                canvas.drawBitmap(mBitmap, srcRect, dstRect, null);
1125666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1126666ea1b28a76aeba74744148b15099254d918671Owen Lin
1127666ea1b28a76aeba74744148b15099254d918671Owen Lin            // Draw the part which has not been loaded.
1128666ea1b28a76aeba74744148b15099254d918671Owen Lin            int isEmpty = ((1 << cols) - 1) & ~mCompletedMask;
1129666ea1b28a76aeba74744148b15099254d918671Owen Lin
1130666ea1b28a76aeba74744148b15099254d918671Owen Lin            if (isEmpty != 0) {
1131666ea1b28a76aeba74744148b15099254d918671Owen Lin                int x = xPos + mSpec.mLeftEdgePadding;
1132666ea1b28a76aeba74744148b15099254d918671Owen Lin                int y = yPos + mSpec.mCellSpacing;
1133666ea1b28a76aeba74744148b15099254d918671Owen Lin
1134666ea1b28a76aeba74744148b15099254d918671Owen Lin                for (int i = 0; i < cols; i++) {
1135666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if ((isEmpty & (1 << i)) != 0) {
1136666ea1b28a76aeba74744148b15099254d918671Owen Lin                        canvas.drawBitmap(mEmptyBitmap, x, y, null);
1137666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
1138666ea1b28a76aeba74744148b15099254d918671Owen Lin                    x += (mSpec.mCellWidth + mSpec.mCellSpacing);
1139666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1140666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1141666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1142666ea1b28a76aeba74744148b15099254d918671Owen Lin
1143666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Mark a request as cancelled. The request has already been removed
1144666ea1b28a76aeba74744148b15099254d918671Owen Lin        // from the queue of ImageLoader, so we only need to mark the fact.
1145666ea1b28a76aeba74744148b15099254d918671Owen Lin        public void cancelRequest(int col) {
1146666ea1b28a76aeba74744148b15099254d918671Owen Lin            int mask = (1 << col);
1147666ea1b28a76aeba74744148b15099254d918671Owen Lin            Assert((mRequestedMask & mask) != 0);
1148666ea1b28a76aeba74744148b15099254d918671Owen Lin            mRequestedMask &= ~mask;
1149666ea1b28a76aeba74744148b15099254d918671Owen Lin            mPendingRequest--;
1150666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1151666ea1b28a76aeba74744148b15099254d918671Owen Lin
1152666ea1b28a76aeba74744148b15099254d918671Owen Lin        // Try to cancel all pending requests for this block. After this
1153666ea1b28a76aeba74744148b15099254d918671Owen Lin        // completes there could still be requests not cancelled (because it is
1154666ea1b28a76aeba74744148b15099254d918671Owen Lin        // already in progress). We deal with that situation by setting mBitmap
1155666ea1b28a76aeba74744148b15099254d918671Owen Lin        // to null in recycle() and check this in loadImageDone().
1156666ea1b28a76aeba74744148b15099254d918671Owen Lin        private void cancelAllRequests() {
1157666ea1b28a76aeba74744148b15099254d918671Owen Lin            for (int i = 0; i < mColumns; i++) {
1158666ea1b28a76aeba74744148b15099254d918671Owen Lin                int mask = (1 << i);
1159666ea1b28a76aeba74744148b15099254d918671Owen Lin                if ((mRequestedMask & mask) != 0) {
1160666ea1b28a76aeba74744148b15099254d918671Owen Lin                    int pos = (mRow * mColumns) + i;
1161666ea1b28a76aeba74744148b15099254d918671Owen Lin                    if (mLoader.cancel(mImageList.getImageAt(pos))) {
1162666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mRequestedMask &= ~mask;
1163666ea1b28a76aeba74744148b15099254d918671Owen Lin                        mPendingRequest--;
1164666ea1b28a76aeba74744148b15099254d918671Owen Lin                    }
1165666ea1b28a76aeba74744148b15099254d918671Owen Lin                }
1166666ea1b28a76aeba74744148b15099254d918671Owen Lin            }
1167666ea1b28a76aeba74744148b15099254d918671Owen Lin        }
1168666ea1b28a76aeba74744148b15099254d918671Owen Lin    }
1169666ea1b28a76aeba74744148b15099254d918671Owen Lin}
1170