BookmarkExpandableView.java revision 9db9529bdb7490ccd72fbe66f7e9c9d7cdd73899
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.browser.view; 18 19import android.content.Context; 20import android.database.Cursor; 21import android.database.DataSetObserver; 22import android.provider.BrowserContract; 23import android.util.AttributeSet; 24import android.view.ContextMenu; 25import android.view.ContextMenu.ContextMenuInfo; 26import android.view.LayoutInflater; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.BaseExpandableListAdapter; 30import android.widget.ExpandableListAdapter; 31import android.widget.ExpandableListView; 32import android.widget.FrameLayout; 33import android.widget.LinearLayout; 34import android.widget.TextView; 35 36import com.android.browser.BookmarkDragHandler; 37import com.android.browser.BookmarkDragHandler.BookmarkDragAdapter; 38import com.android.browser.BookmarkDragHandler.BookmarkDragState; 39import com.android.browser.BreadCrumbView; 40import com.android.browser.BrowserBookmarksAdapter; 41import com.android.browser.BrowserBookmarksPage; 42import com.android.browser.BrowserBookmarksPage.ExtraDragState; 43import com.android.browser.R; 44import com.android.internal.view.menu.MenuBuilder; 45 46import java.util.ArrayList; 47import java.util.HashMap; 48 49public class BookmarkExpandableView extends ExpandableListView 50 implements BreadCrumbView.Controller { 51 52 private BookmarkAccountAdapter mAdapter; 53 private int mColumnWidth; 54 private Context mContext; 55 private OnChildClickListener mOnChildClickListener; 56 private ContextMenuInfo mContextMenuInfo = null; 57 private OnCreateContextMenuListener mOnCreateContextMenuListener; 58 private boolean mLongClickable; 59 private BreadCrumbView.Controller mBreadcrumbController; 60 private BookmarkDragHandler mDragHandler; 61 private int mMaxColumnCount; 62 private int mCurrentView = -1; 63 64 public BookmarkExpandableView(Context context) { 65 super(context); 66 init(context); 67 } 68 69 public BookmarkExpandableView(Context context, AttributeSet attrs) { 70 super(context, attrs); 71 init(context); 72 } 73 74 public BookmarkExpandableView( 75 Context context, AttributeSet attrs, int defStyle) { 76 super(context, attrs, defStyle); 77 init(context); 78 } 79 80 void init(Context context) { 81 mContext = context; 82 setItemsCanFocus(true); 83 setLongClickable(false); 84 mMaxColumnCount = mContext.getResources() 85 .getInteger(R.integer.max_bookmark_columns); 86 mAdapter = new BookmarkAccountAdapter(mContext); 87 super.setAdapter(mAdapter); 88 } 89 90 @Override 91 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 92 int width = MeasureSpec.getSize(widthMeasureSpec); 93 int widthMode = MeasureSpec.getMode(widthMeasureSpec); 94 if (width > 0) { 95 mAdapter.measureChildren(width); 96 if (mAdapter.mRowPadding > 0) { 97 width -= mAdapter.mRowPadding * 2; 98 } 99 widthMeasureSpec = MeasureSpec.makeMeasureSpec(width, widthMode); 100 } 101 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 102 if (width != getMeasuredWidth()) { 103 mAdapter.measureChildren(getMeasuredWidth()); 104 } 105 } 106 107 @Override 108 public void setAdapter(ExpandableListAdapter adapter) { 109 throw new RuntimeException("Not supported"); 110 } 111 112 public void setColumnWidthFromLayout(int layout) { 113 LayoutInflater infalter = LayoutInflater.from(mContext); 114 View v = infalter.inflate(layout, this, false); 115 v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 116 mColumnWidth = v.getMeasuredWidth(); 117 } 118 119 public void clearAccounts() { 120 mAdapter.clear(); 121 } 122 123 public void addAccount(String accountName, BrowserBookmarksAdapter adapter) { 124 // First, check if it already exists 125 int indexOf = mAdapter.mGroups.indexOf(accountName); 126 if (indexOf >= 0) { 127 BrowserBookmarksAdapter existing = mAdapter.mChildren.get(indexOf); 128 if (existing != adapter) { 129 existing.unregisterDataSetObserver(mAdapter.mObserver); 130 // Replace the existing one 131 mAdapter.mChildren.remove(indexOf); 132 mAdapter.mChildren.add(indexOf, adapter); 133 adapter.registerDataSetObserver(mAdapter.mObserver); 134 } 135 } else { 136 if (mCurrentView >= 0) { 137 adapter.selectView(mCurrentView); 138 } 139 mAdapter.mGroups.add(accountName); 140 mAdapter.mChildren.add(adapter); 141 adapter.registerDataSetObserver(mAdapter.mObserver); 142 } 143 mAdapter.notifyDataSetChanged(); 144 expandGroup(mAdapter.getGroupCount() - 1); 145 } 146 147 @Override 148 public void setOnChildClickListener(OnChildClickListener onChildClickListener) { 149 mOnChildClickListener = onChildClickListener; 150 } 151 152 @Override 153 public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) { 154 mOnCreateContextMenuListener = l; 155 if (!mLongClickable) { 156 mLongClickable = true; 157 if (mAdapter != null) { 158 mAdapter.notifyDataSetChanged(); 159 } 160 } 161 } 162 163 @Override 164 public void createContextMenu(ContextMenu menu) { 165 // The below is copied from View - we want to bypass the override 166 // in AbsListView 167 168 ContextMenuInfo menuInfo = getContextMenuInfo(); 169 170 // Sets the current menu info so all items added to menu will have 171 // my extra info set. 172 ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo); 173 174 onCreateContextMenu(menu); 175 if (mOnCreateContextMenuListener != null) { 176 mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo); 177 } 178 179 // Clear the extra information so subsequent items that aren't mine don't 180 // have my extra info. 181 ((MenuBuilder)menu).setCurrentMenuInfo(null); 182 183 if (mParent != null) { 184 mParent.createContextMenu(menu); 185 } 186 } 187 188 @Override 189 public boolean showContextMenuForChild(View originalView) { 190 int groupPosition = (Integer) originalView.getTag(R.id.group_position); 191 int childPosition = (Integer) originalView.getTag(R.id.child_position); 192 193 mContextMenuInfo = new BookmarkContextMenuInfo(childPosition, 194 groupPosition); 195 if (getParent() != null) { 196 getParent().showContextMenuForChild(this); 197 } 198 199 return true; 200 } 201 202 @Override 203 public void onTop(BreadCrumbView view, int level, Object data) { 204 if (mBreadcrumbController != null) { 205 mBreadcrumbController.onTop(view, level, data); 206 } 207 } 208 209 public void setBreadcrumbController(BreadCrumbView.Controller controller) { 210 mBreadcrumbController = controller; 211 } 212 213 @Override 214 protected ContextMenuInfo getContextMenuInfo() { 215 return mContextMenuInfo; 216 } 217 218 public BrowserBookmarksAdapter getChildAdapter(int groupPosition) { 219 return mAdapter.mChildren.get(groupPosition); 220 } 221 222 public BookmarkDragAdapter getDragAdapter() { 223 return mDragAdapter; 224 } 225 226 public void showContextMenuForState(BookmarkDragState state) { 227 ExtraDragState extraState = (ExtraDragState) state.extraState; 228 mContextMenuInfo = new BookmarkContextMenuInfo( 229 extraState.childPosition, 230 extraState.groupPosition); 231 if (getParent() != null) { 232 getParent().showContextMenuForChild(BookmarkExpandableView.this); 233 } 234 } 235 236 private BookmarkDragAdapter mDragAdapter = new BookmarkDragAdapter() { 237 238 @Override 239 public void setBookmarkDragHandler(BookmarkDragHandler handler) { 240 mDragHandler = handler; 241 } 242 243 @Override 244 public Cursor getItemForView(View v) { 245 int groupPosition = (Integer) v.getTag(R.id.group_position); 246 int childPosition = (Integer) v.getTag(R.id.child_position); 247 return getChildAdapter(groupPosition).getItem(childPosition); 248 } 249 250 }; 251 252 private OnClickListener mChildClickListener = new OnClickListener() { 253 254 @Override 255 public void onClick(View v) { 256 int groupPosition = (Integer) v.getTag(R.id.group_position); 257 int childPosition = (Integer) v.getTag(R.id.child_position); 258 long id = (Long) v.getTag(R.id.child_id); 259 if (mOnChildClickListener != null) { 260 mOnChildClickListener.onChildClick(BookmarkExpandableView.this, 261 v, groupPosition, childPosition, id); 262 } 263 } 264 }; 265 266 private OnClickListener mGroupOnClickListener = new OnClickListener() { 267 268 @Override 269 public void onClick(View v) { 270 int groupPosition = (Integer) v.getTag(R.id.group_position); 271 if (isGroupExpanded(groupPosition)) { 272 collapseGroup(groupPosition); 273 } else { 274 expandGroup(groupPosition, true); 275 } 276 } 277 }; 278 279 private OnLongClickListener mChildOnLongClickListener = new OnLongClickListener() { 280 281 @Override 282 public boolean onLongClick(View v) { 283 ExtraDragState state = new ExtraDragState(); 284 state.groupPosition = (Integer) v.getTag(R.id.group_position); 285 state.childPosition = (Integer) v.getTag(R.id.child_position); 286 long id = (Long) v.getTag(R.id.child_id); 287 Cursor c = getChildAdapter(state.groupPosition) 288 .getItem(state.childPosition); 289 return mDragHandler.startDrag(v, c, id, state); 290 } 291 }; 292 293 public BreadCrumbView getBreadCrumbs(int groupPosition) { 294 return mAdapter.getBreadCrumbView(groupPosition); 295 } 296 297 public void selectView(int view) { 298 mCurrentView = view; 299 for (BrowserBookmarksAdapter adapter : mAdapter.mChildren) { 300 adapter.selectView(mCurrentView); 301 } 302 mAdapter.notifyDataSetChanged(); 303 } 304 305 class BookmarkAccountAdapter extends BaseExpandableListAdapter { 306 ArrayList<BrowserBookmarksAdapter> mChildren; 307 ArrayList<String> mGroups; 308 HashMap<Integer, BreadCrumbView> mBreadcrumbs = 309 new HashMap<Integer, BreadCrumbView>(); 310 LayoutInflater mInflater; 311 int mRowCount = 1; // assume at least 1 child fits in a row 312 int mLastViewWidth = -1; 313 int mRowPadding = -1; 314 DataSetObserver mObserver = new DataSetObserver() { 315 @Override 316 public void onChanged() { 317 notifyDataSetChanged(); 318 } 319 320 @Override 321 public void onInvalidated() { 322 notifyDataSetChanged(); 323 } 324 }; 325 326 public BookmarkAccountAdapter(Context context) { 327 mContext = context; 328 mInflater = LayoutInflater.from(mContext); 329 mChildren = new ArrayList<BrowserBookmarksAdapter>(); 330 mGroups = new ArrayList<String>(); 331 } 332 333 public void clear() { 334 mGroups.clear(); 335 mChildren.clear(); 336 notifyDataSetChanged(); 337 } 338 339 @Override 340 public Object getChild(int groupPosition, int childPosition) { 341 return mChildren.get(groupPosition).getItem(childPosition); 342 } 343 344 @Override 345 public long getChildId(int groupPosition, int childPosition) { 346 return childPosition; 347 } 348 349 @Override 350 public View getChildView(int groupPosition, int childPosition, 351 boolean isLastChild, View convertView, ViewGroup parent) { 352 if (convertView == null) { 353 convertView = mInflater.inflate(R.layout.bookmark_grid_row, parent, false); 354 } 355 BrowserBookmarksAdapter childAdapter = mChildren.get(groupPosition); 356 int rowCount = mRowCount; 357 if (childAdapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) { 358 rowCount = 1; 359 } 360 LinearLayout row = (LinearLayout) convertView; 361 if (row.getChildCount() > rowCount) { 362 row.removeViews(rowCount, row.getChildCount() - rowCount); 363 } 364 for (int i = 0; i < rowCount; i++) { 365 View cv = null; 366 if (row.getChildCount() > i) { 367 cv = row.getChildAt(i); 368 } 369 int realChildPosition = (childPosition * rowCount) + i; 370 if (realChildPosition < childAdapter.getCount()) { 371 View v = childAdapter.getView(realChildPosition, cv, row); 372 v.setTag(R.id.group_position, groupPosition); 373 v.setTag(R.id.child_position, realChildPosition); 374 v.setTag(R.id.child_id, childAdapter.getItemId(realChildPosition)); 375 v.setOnClickListener(mChildClickListener); 376 v.setLongClickable(mLongClickable); 377 if (mDragHandler != null) { 378 v.setOnLongClickListener(mChildOnLongClickListener); 379 mDragHandler.registerBookmarkDragHandler(v); 380 } 381 if (cv == null) { 382 row.addView(v); 383 } else if (cv != v) { 384 row.removeViewAt(i); 385 row.addView(v, i); 386 } else { 387 cv.setVisibility(View.VISIBLE); 388 } 389 } else if (cv != null) { 390 cv.setVisibility(View.GONE); 391 } 392 } 393 return row; 394 } 395 396 @Override 397 public int getChildrenCount(int groupPosition) { 398 BrowserBookmarksAdapter adapter = mChildren.get(groupPosition); 399 if (adapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) { 400 return adapter.getCount(); 401 } 402 return (int) Math.ceil(adapter.getCount() / (float)mRowCount); 403 } 404 405 @Override 406 public Object getGroup(int groupPosition) { 407 return mChildren.get(groupPosition); 408 } 409 410 @Override 411 public int getGroupCount() { 412 return mGroups.size(); 413 } 414 415 public void measureChildren(int viewWidth) { 416 if (mLastViewWidth == viewWidth) return; 417 418 int rowCount = viewWidth / mColumnWidth; 419 if (mMaxColumnCount > 0) { 420 rowCount = Math.min(rowCount, mMaxColumnCount); 421 } 422 int rowPadding = (viewWidth - (rowCount * mColumnWidth)) / 2; 423 boolean notify = rowCount != mRowCount || rowPadding != mRowPadding; 424 mRowCount = rowCount; 425 mRowPadding = rowPadding; 426 mLastViewWidth = viewWidth; 427 if (notify) { 428 notifyDataSetChanged(); 429 } 430 } 431 432 @Override 433 public long getGroupId(int groupPosition) { 434 return groupPosition; 435 } 436 437 @Override 438 public View getGroupView(int groupPosition, boolean isExpanded, 439 View view, ViewGroup parent) { 440 if (view == null) { 441 view = mInflater.inflate(R.layout.bookmark_group_view, parent, false); 442 view.setOnClickListener(mGroupOnClickListener); 443 } 444 view.setTag(R.id.group_position, groupPosition); 445 FrameLayout crumbHolder = (FrameLayout) view.findViewById(R.id.crumb_holder); 446 crumbHolder.removeAllViews(); 447 BreadCrumbView crumbs = getBreadCrumbView(groupPosition); 448 if (crumbs.getParent() != null) { 449 ((ViewGroup)crumbs.getParent()).removeView(crumbs); 450 } 451 crumbHolder.addView(crumbs); 452 TextView name = (TextView) view.findViewById(R.id.group_name); 453 String groupName = mGroups.get(groupPosition); 454 if (groupName == null) { 455 groupName = mContext.getString(R.string.local_bookmarks); 456 } 457 name.setText(groupName); 458 return view; 459 } 460 461 public BreadCrumbView getBreadCrumbView(int groupPosition) { 462 BreadCrumbView crumbs = mBreadcrumbs.get(groupPosition); 463 if (crumbs == null) { 464 crumbs = (BreadCrumbView) 465 mInflater.inflate(R.layout.bookmarks_header, null); 466 crumbs.setController(BookmarkExpandableView.this); 467 crumbs.setUseBackButton(true); 468 crumbs.setMaxVisible(2); 469 String bookmarks = mContext.getString(R.string.bookmarks); 470 crumbs.pushView(bookmarks, false, 471 BrowserContract.Bookmarks.CONTENT_URI_DEFAULT_FOLDER); 472 crumbs.setTag(R.id.group_position, groupPosition); 473 mBreadcrumbs.put(groupPosition, crumbs); 474 } 475 return crumbs; 476 } 477 478 @Override 479 public boolean hasStableIds() { 480 return false; 481 } 482 483 @Override 484 public boolean isChildSelectable(int groupPosition, int childPosition) { 485 return true; 486 } 487 488 @Override 489 public int getChildTypeCount() { 490 return 2; 491 } 492 493 @Override 494 public int getChildType(int groupPosition, int childPosition) { 495 BrowserBookmarksAdapter adapter = mChildren.get(groupPosition); 496 if (adapter.getViewMode() == BrowserBookmarksPage.VIEW_LIST) { 497 return 1; 498 } 499 return 0; 500 } 501 } 502 503 public static class BookmarkContextMenuInfo implements ContextMenuInfo { 504 505 private BookmarkContextMenuInfo(int childPosition, int groupPosition) { 506 this.childPosition = childPosition; 507 this.groupPosition = groupPosition; 508 } 509 510 public int childPosition; 511 public int groupPosition; 512 } 513 514} 515