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