Android custom Grid View with zoom-in animation effect
In a Grid View all the views are arranged in the grid fashion. We can customize a Grid View by creating a custom layout for the contents of the grid item.
Here in this example we create a Grid View with the grids containing a simple layout have an Image View that holds a thumbnail.
We have different cases where we need to display those thumbs are the grid views in a separate layout with larger images. We can achieve this by starting a new Activity on a grid item click with the image reference and showing it in that activity.
But here in the example on a grid item (thumbnail) click, we zoom-in form the thumbnail to full screen image with a growing animation effect.
Here’s the Demo of the final Project:
Here are the steps in creating the project:
step 1: Create a New Android Project with a blank activity(MainActivity.java) and a layout(activity_main.xml).
step 2: To the main layout(activity_main.xml) Add a Frame Layout as the root and then a Grid View followed by an Image View – to display the zoomed-in image of the thumbnail:
Note: We use frame layout because we are initially hiding the full screen ImageView and showing it on click on the Grid Item. As all the Views in a frame layout are stacked one on other we can display a View over another View.
For more info on Frame Layouts refer: http://developer.android.com/reference/android/widget/FrameLayout.html
activity_main.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<FrameLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=“@+id/container”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent” >
<GridView
android:id=“@+id/gridView”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:columnWidth=“80dp”
android:horizontalSpacing=“10dp”
android:numColumns=“auto_fit”
android:stretchMode=“columnWidth”
android:verticalSpacing=“10dp” >
</GridView>
<ImageView
android:id=“@+id/expanded_image”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:contentDescription=“@string/app_name”
android:visibility=“invisible” />
</FrameLayout>
|
step 3: Create a new layout for the Grid Item (grid_item.xml) with an Image View to hold the thumbnail:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<RelativeLayout xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:padding=“5dp” >
<ImageView
android:id=“@+id/thumb”
android:layout_width=“72dp”
android:layout_height=“72dp”
android:layout_centerHorizontal=“true”
android:contentDescription=“@string/app_name” />
</RelativeLayout>
|
step 4:
The final main Activity(MainActivity.java) looks as follows (Commented wherever necessary for better understanding the flow) :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
|
public class MainActivity extends Activity {
// Declare variables / references
private GridView gv;
private Animator mCurrentAnimator;
private int mShortAnimationDuration;
// Create Array thumbs resource id’s:
private int thumb[] = { R.drawable.cupcake, R.drawable.donut,
R.drawable.eclair, R.drawable.froyo, R.drawable.gingerbread,
R.drawable.honeycomb, R.drawable.icecreamsandwich,
R.drawable.jellybean, R.drawable.cupcake, R.drawable.donut,
R.drawable.eclair, R.drawable.froyo, R.drawable.gingerbread,
R.drawable.honeycomb, R.drawable.icecreamsandwich,
R.drawable.jellybean };
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Initialize the variables:
gv = (GridView) findViewById(R.id.gridView);
// Set an Adapter to the ListView
gv.setAdapter(new ImageAdapter(this));
// Set on item click listener to the ListView
gv.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View v, int pos,
long id) {
// Display the zoomed in image in full screen
zoomImageFromThumb(v, thumb[pos]);
}
});
// Set the Animation time form the android defaults
mShortAnimationDuration = getResources().getInteger(
android.R.integer.config_shortAnimTime);
}
// Create an Adapter Class extending the BaseAdapter
class ImageAdapter extends BaseAdapter {
private LayoutInflater layoutInflater;
public ImageAdapter(MainActivity activity) {
// TODO Auto-generated constructor stub
layoutInflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
// Set the count value to the total number of items in the Array
return thumb.length;
}
@Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// Inflate the item layout and set the views
View listItem = convertView;
int pos = position;
if (listItem == null) {
listItem = layoutInflater.inflate(R.layout.grid_item, null);
}
// Initialize the views in the layout
ImageView iv = (ImageView) listItem.findViewById(R.id.thumb);
// Set the views in the layout
iv.setBackgroundResource(thumb[pos]);
return listItem;
}
}
private void zoomImageFromThumb(final View thumbView, int imageResId) {
// If there’s an animation in progress, cancel it immediately and
// proceed with this one.
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Load the high-resolution “zoomed-in” image.
final ImageView expandedImageView = (ImageView) findViewById(R.id.expanded_image);
expandedImageView.setImageResource(imageResId);
// Calculate the starting and ending bounds for the zoomed-in image.
// This step
// involves lots of math. Yay, math.
final Rect startBounds = new Rect();
final Rect finalBounds = new Rect();
final Point globalOffset = new Point();
// The start bounds are the global visible rectangle of the thumbnail,
// and the
// final bounds are the global visible rectangle of the container view.
// Also
// set the container view’s offset as the origin for the bounds, since
// that’s
// the origin for the positioning animation properties (X, Y).
thumbView.getGlobalVisibleRect(startBounds);
findViewById(R.id.container).getGlobalVisibleRect(finalBounds,
globalOffset);
startBounds.offset(–globalOffset.x, –globalOffset.y);
finalBounds.offset(–globalOffset.x, –globalOffset.y);
// Adjust the start bounds to be the same aspect ratio as the final
// bounds using the
// “center crop” technique. This prevents undesirable stretching during
// the animation.
// Also calculate the start scaling factor (the end scaling factor is
// always 1.0).
float startScale;
if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds
.width() / startBounds.height()) {
// Extend start bounds horizontally
startScale = (float) startBounds.height() / finalBounds.height();
float startWidth = startScale * finalBounds.width();
float deltaWidth = (startWidth – startBounds.width()) / 2;
startBounds.left -= deltaWidth;
startBounds.right += deltaWidth;
} else {
// Extend start bounds vertically
startScale = (float) startBounds.width() / finalBounds.width();
float startHeight = startScale * finalBounds.height();
float deltaHeight = (startHeight – startBounds.height()) / 2;
startBounds.top -= deltaHeight;
startBounds.bottom += deltaHeight;
}
// Hide the thumbnail and show the zoomed-in view. When the animation
// begins,
// it will position the zoomed-in view in the place of the thumbnail.
thumbView.setAlpha(0f);
expandedImageView.setVisibility(View.VISIBLE);
// Set the pivot point for SCALE_X and SCALE_Y transformations to the
// top-left corner of
// the zoomed-in view (the default is the center of the view).
expandedImageView.setPivotX(0f);
expandedImageView.setPivotY(0f);
// Construct and run the parallel animation of the four translation and
// scale properties
// (X, Y, SCALE_X, and SCALE_Y).
AnimatorSet set = new AnimatorSet();
set.play(
ObjectAnimator.ofFloat(expandedImageView, View.X,
startBounds.left, finalBounds.left))
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
startBounds.top, finalBounds.top))
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
startScale, 1f))
.with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_Y,
startScale, 1f));
set.setDuration(mShortAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mCurrentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
mCurrentAnimator = null;
}
});
set.start();
mCurrentAnimator = set;
// Upon clicking the zoomed-in image, it should zoom back down to the
// original bounds
// and show the thumbnail instead of the expanded image.
final float startScaleFinal = startScale;
expandedImageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (mCurrentAnimator != null) {
mCurrentAnimator.cancel();
}
// Animate the four positioning/sizing properties in parallel,
// back to their
// original values.
AnimatorSet set = new AnimatorSet();
set.play(
ObjectAnimator.ofFloat(expandedImageView, View.X,
startBounds.left))
.with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
startBounds.top))
.with(ObjectAnimator.ofFloat(expandedImageView,
View.SCALE_X, startScaleFinal))
.with(ObjectAnimator.ofFloat(expandedImageView,
View.SCALE_Y, startScaleFinal));
set.setDuration(mShortAnimationDuration);
set.setInterpolator(new DecelerateInterpolator());
set.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
thumbView.setAlpha(1f);
expandedImageView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
@Override
public void onAnimationCancel(Animator animation) {
thumbView.setAlpha(1f);
expandedImageView.setVisibility(View.GONE);
mCurrentAnimator = null;
}
});
set.start();
mCurrentAnimator = set;
}
});
}
}
|