ProjectPickerAdapter.java revision 4c37a4611cc04128fcd28429f92380f73540b5db
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.videoeditor; 18 19import android.content.Context; 20import android.content.res.Resources; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.graphics.Canvas; 24import android.graphics.Color; 25import android.graphics.Paint; 26import android.graphics.Paint.Style; 27import android.graphics.Typeface; 28import android.media.videoeditor.VideoEditor; 29import android.os.AsyncTask; 30import android.text.format.DateUtils; 31import android.util.LruCache; 32import android.view.LayoutInflater; 33import android.view.View; 34import android.view.ViewGroup; 35import android.widget.BaseAdapter; 36import android.widget.ImageView; 37import android.widget.TextView; 38 39import com.android.videoeditor.service.VideoEditorProject; 40import com.android.videoeditor.util.ImageUtils; 41 42import java.io.File; 43import java.io.IOException; 44import java.util.List; 45 46 47public class ProjectPickerAdapter extends BaseAdapter { 48 private Context mContext; 49 private Resources mResources; 50 private LayoutInflater mInflater; 51 private List<VideoEditorProject> mProjects; 52 private int mItemWidth; 53 private int mItemHeight; 54 private int mOverlayHeight; 55 private int mOverlayVerticalInset; 56 private int mOverlayHorizontalInset; 57 private LruCache<String, Bitmap> mPreviewBitmapCache; 58 59 public ProjectPickerAdapter(Context context, LayoutInflater inflater, 60 List<VideoEditorProject> projects) { 61 mContext = context; 62 mResources = context.getResources(); 63 mInflater = inflater; 64 mProjects = projects; 65 mItemWidth = (int) mResources.getDimension(R.dimen.project_picker_item_width); 66 mItemHeight = (int) mResources.getDimension(R.dimen.project_picker_item_height); 67 mOverlayHeight = (int) mResources.getDimension( 68 R.dimen.project_picker_item_overlay_height); 69 mOverlayVerticalInset = (int) mResources.getDimension( 70 R.dimen.project_picker_item_overlay_vertical_inset); 71 mOverlayHorizontalInset = (int) mResources.getDimension( 72 R.dimen.project_picker_item_overlay_horizontal_inset); 73 // Limit the cache size to 15 thumbnails. 74 mPreviewBitmapCache = new LruCache<String, Bitmap>(15); 75 } 76 77 /** 78 * Clears project list and update display. 79 */ 80 public void clear() { 81 mPreviewBitmapCache.evictAll(); 82 mProjects.clear(); 83 notifyDataSetChanged(); 84 } 85 86 /** 87 * Removes the project with specified {@code projectPath} from the project list and updates the 88 * display. 89 * 90 * @param projectPath The project path of the to-be-removed project 91 * @return {@code true} if the project is successfully removed, 92 * {@code false} if no removal happened 93 */ 94 public boolean remove(String projectPath) { 95 for (VideoEditorProject project : mProjects) { 96 if (project.getPath().equals(projectPath)) { 97 if (mProjects.remove(project)) { 98 notifyDataSetChanged(); 99 return true; 100 } else { 101 return false; 102 } 103 } 104 } 105 return false; 106 } 107 108 @Override 109 public int getCount() { 110 // Add one to represent an additional dummy project for "create new project" option. 111 return mProjects.size() + 1; 112 } 113 114 @Override 115 public Object getItem(int position) { 116 if (position == mProjects.size()) { 117 return null; 118 } 119 return mProjects.get(position); 120 } 121 122 @Override 123 public long getItemId(int position) { 124 return position; 125 } 126 127 @Override 128 public View getView(int position, View convertView, ViewGroup parent) { 129 // Inflate a new view with project thumbnail and information. 130 // We never reuse convertView because we load thumbnails asynchronously 131 // and hook an async task with the new view. If the new view is reused 132 // as a convertView, the async task might put a wrong thumbnail on it. 133 View v = mInflater.inflate(R.layout.project_picker_item, null); 134 ImageView iv = (ImageView) v.findViewById(R.id.thumbnail); 135 Bitmap thumbnail; 136 String title; 137 String duration; 138 if (position == mProjects.size()) { 139 title = mContext.getString(R.string.projects_new_project); 140 duration = ""; 141 thumbnail = renderNewProjectThumbnail(); 142 } else { 143 VideoEditorProject project = mProjects.get(position); 144 title = project.getName(); 145 duration = millisecondsToTimeString(project.getProjectDuration()); 146 thumbnail = getThumbnail(project.getPath(), iv, title, duration); 147 } 148 149 if (thumbnail != null) { 150 drawBottomOverlay(thumbnail, title, duration); 151 iv.setImageBitmap(thumbnail); 152 } 153 154 return v; 155 } 156 157 /** 158 * Draws transparent black bottom overlay with movie title and duration on the bitmap. 159 */ 160 public void drawBottomOverlay(Bitmap bitmap, String title, String duration) { 161 // Draw overlay at the bottom of the canvas. 162 final Canvas canvas = new Canvas(bitmap); 163 final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 164 paint.setColor(Color.BLACK); 165 paint.setAlpha(128); 166 final int left = 0, top = bitmap.getHeight() - mOverlayHeight, 167 right = bitmap.getWidth(), bottom = bitmap.getHeight(); 168 canvas.drawRect(left, top, right, bottom, paint); 169 170 // Draw movie title at the left of the overlay. 171 paint.setColor(Color.WHITE); 172 paint.setTextSize((int) mResources.getDimension( 173 R.dimen.project_picker_item_font_size)); 174 canvas.drawText(title, mOverlayHorizontalInset, 175 bitmap.getHeight() - mOverlayHeight + mOverlayVerticalInset, 176 paint); 177 178 // Draw movie duration at the right of the overlay. 179 canvas.drawText(duration, 180 bitmap.getWidth() - paint.measureText(duration) - mOverlayHorizontalInset, 181 bitmap.getHeight() - mOverlayHeight + mOverlayVerticalInset, 182 paint); 183 } 184 185 private Bitmap getThumbnail(String projectPath, ImageView imageView, String title, 186 String duration) { 187 Bitmap previewBitmap = mPreviewBitmapCache.get(projectPath); 188 if (previewBitmap == null) { 189 // Cache miss: asynchronously load bitmap to avoid scroll stuttering 190 // in the project picker. 191 new LoadPreviewBitmapTask(this, projectPath, imageView, mItemWidth, mItemHeight, 192 title, duration, mPreviewBitmapCache).execute(); 193 } else { 194 return previewBitmap; 195 } 196 197 return null; 198 } 199 200 private Bitmap renderNewProjectThumbnail() { 201 final Bitmap bitmap = Bitmap.createBitmap(mItemWidth, mItemHeight, 202 Bitmap.Config.ARGB_8888); 203 final Canvas canvas = new Canvas(bitmap); 204 final Paint paint = new Paint(); 205 canvas.drawRect(0, 0, mItemWidth, mItemHeight, paint); 206 207 paint.setTextSize(18.0f); 208 paint.setAlpha(255); 209 final Bitmap newProjectIcon = BitmapFactory.decodeResource(mResources, 210 R.drawable.add_video_project_big); 211 final int x = (mItemWidth - newProjectIcon.getWidth()) / 2; 212 final int y = (mItemHeight - newProjectIcon.getHeight()) / 2; 213 canvas.drawBitmap(newProjectIcon, x, y, paint); 214 newProjectIcon.recycle(); 215 216 return bitmap; 217 } 218 219 /** 220 * Converts milliseconds into the string time format HH:mm:ss. 221 */ 222 private String millisecondsToTimeString(long milliseconds) { 223 return DateUtils.formatElapsedTime(milliseconds / 1000); 224 } 225} 226 227/** 228 * Worker that loads preview bitmap for a project, 229 */ 230class LoadPreviewBitmapTask extends AsyncTask<Void, Void, Bitmap> { 231 // Handle to the adapter that initiates this async task. 232 private ProjectPickerAdapter mContextAdapter; 233 private String mProjectPath; 234 // Handle to the image view we should update when the preview bitmap is loaded. 235 private ImageView mImageView; 236 private int mWidth; 237 private int mHeight; 238 private String mTitle; 239 private String mDuration; 240 private LruCache<String, Bitmap> mPreviewBitmapCache; 241 242 public LoadPreviewBitmapTask(ProjectPickerAdapter contextAdapter, String projectPath, 243 ImageView imageView, int width, int height, String title, String duration, 244 LruCache<String, Bitmap> previewBitmapCache) { 245 mContextAdapter = contextAdapter; 246 mProjectPath = projectPath; 247 mImageView = imageView; 248 mWidth = width; 249 mHeight = height; 250 mTitle = title; 251 mDuration = duration; 252 mPreviewBitmapCache = previewBitmapCache; 253 } 254 255 @Override 256 protected Bitmap doInBackground(Void... param) { 257 final File thumbnail = new File(mProjectPath, VideoEditor.THUMBNAIL_FILENAME); 258 // Return early if thumbnail does not exist. 259 if (!thumbnail.exists()) { 260 return null; 261 } 262 263 try { 264 final Bitmap previewBitmap = ImageUtils.scaleImage( 265 thumbnail.getAbsolutePath(), 266 mWidth, 267 mHeight, 268 ImageUtils.MATCH_LARGER_DIMENSION); 269 if (previewBitmap != null) { 270 final Bitmap bitmap = Bitmap.createBitmap(mWidth, mHeight, 271 Bitmap.Config.ARGB_8888); 272 final Canvas canvas = new Canvas(bitmap); 273 final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); 274 275 // Draw bitmap at the center of the canvas. 276 canvas.drawBitmap(previewBitmap, 277 (mWidth - previewBitmap.getWidth()) / 2, 278 (mHeight - previewBitmap.getHeight()) / 2, 279 paint); 280 return bitmap; 281 } 282 } catch (IOException e) { 283 e.printStackTrace(); 284 } 285 286 return null; 287 } 288 289 @Override 290 protected void onPostExecute(Bitmap result) { 291 if (result == null) { 292 // If we don't have thumbnail, default to a black canvas. 293 result = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 294 result.eraseColor(Color.BLACK); 295 } else { 296 mPreviewBitmapCache.put(mProjectPath, result); 297 } 298 299 // Update the image view. 300 mContextAdapter.drawBottomOverlay(result, mTitle, mDuration); 301 mImageView.setImageBitmap(result); 302 } 303} 304