BreadCrumbView.java revision 71efc2bbf08574425a387c992e24cb9eaf0a6e6c
1/* 2 * Copyright (C) 2010 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; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.drawable.Drawable; 22import android.text.TextUtils; 23import android.util.AttributeSet; 24import android.util.TypedValue; 25import android.view.Gravity; 26import android.view.View; 27import android.view.View.OnClickListener; 28import android.widget.ImageButton; 29import android.widget.ImageView; 30import android.widget.LinearLayout; 31import android.widget.TextView; 32 33import java.util.ArrayList; 34import java.util.List; 35 36/** 37 * Simple bread crumb view 38 * Use setController to receive callbacks from user interactions 39 * Use pushView, popView, clear, and getTopData to change/access the view stack 40 */ 41public class BreadCrumbView extends LinearLayout implements OnClickListener { 42 private static final int DIVIDER_PADDING = 12; // dips 43 44 public interface Controller { 45 public void onTop(BreadCrumbView view, int level, Object data); 46 } 47 48 private ImageButton mBackButton; 49 private Controller mController; 50 private List<Crumb> mCrumbs; 51 private boolean mUseBackButton; 52 private Drawable mSeparatorDrawable; 53 private float mDividerPadding; 54 private int mMaxVisible = -1; 55 private Context mContext; 56 57 /** 58 * @param context 59 * @param attrs 60 * @param defStyle 61 */ 62 public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) { 63 super(context, attrs, defStyle); 64 init(context); 65 } 66 67 /** 68 * @param context 69 * @param attrs 70 */ 71 public BreadCrumbView(Context context, AttributeSet attrs) { 72 super(context, attrs); 73 init(context); 74 } 75 76 /** 77 * @param context 78 */ 79 public BreadCrumbView(Context context) { 80 super(context); 81 init(context); 82 } 83 84 private void init(Context ctx) { 85 mContext = ctx; 86 setFocusable(true); 87 mUseBackButton = false; 88 mCrumbs = new ArrayList<Crumb>(); 89 TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme); 90 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical); 91 a.recycle(); 92 mDividerPadding = DIVIDER_PADDING * mContext.getResources().getDisplayMetrics().density; 93 addBackButton(); 94 } 95 96 public void setUseBackButton(boolean useflag) { 97 mUseBackButton = useflag; 98 updateVisible(); 99 } 100 101 public void setController(Controller ctl) { 102 mController = ctl; 103 } 104 105 public int getMaxVisible() { 106 return mMaxVisible; 107 } 108 109 public void setMaxVisible(int max) { 110 mMaxVisible = max; 111 updateVisible(); 112 } 113 114 public int getTopLevel() { 115 return mCrumbs.size(); 116 } 117 118 public Object getTopData() { 119 Crumb c = getTopCrumb(); 120 if (c != null) { 121 return c.data; 122 } 123 return null; 124 } 125 126 public int size() { 127 return mCrumbs.size(); 128 } 129 130 public void clear() { 131 while (mCrumbs.size() > 1) { 132 pop(false); 133 } 134 pop(true); 135 } 136 137 public void notifyController() { 138 if (mController != null) { 139 if (mCrumbs.size() > 0) { 140 mController.onTop(this, mCrumbs.size(), getTopCrumb().data); 141 } else { 142 mController.onTop(this, 0, null); 143 } 144 } 145 } 146 147 public View pushView(String name, Object data) { 148 return pushView(name, true, data); 149 } 150 151 public View pushView(String name, boolean canGoBack, Object data) { 152 Crumb crumb = new Crumb(name, canGoBack, data); 153 pushCrumb(crumb); 154 return crumb.crumbView; 155 } 156 157 public void pushView(View view, Object data) { 158 Crumb crumb = new Crumb(view, true, data); 159 pushCrumb(crumb); 160 } 161 162 public void popView() { 163 pop(true); 164 } 165 166 private void addBackButton() { 167 mBackButton = new ImageButton(mContext); 168 mBackButton.setImageResource(R.drawable.ic_back_hierarchy_holo_dark); 169 TypedValue outValue = new TypedValue(); 170 getContext().getTheme().resolveAttribute( 171 android.R.attr.selectableItemBackground, outValue, true); 172 int resid = outValue.resourceId; 173 mBackButton.setBackgroundResource(resid); 174 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 175 LayoutParams.MATCH_PARENT)); 176 mBackButton.setOnClickListener(this); 177 mBackButton.setVisibility(View.GONE); 178 addView(mBackButton, 0); 179 } 180 181 private void pushCrumb(Crumb crumb) { 182 if (mCrumbs.size() > 0) { 183 addSeparator(); 184 } 185 mCrumbs.add(crumb); 186 addView(crumb.crumbView); 187 updateVisible(); 188 crumb.crumbView.setOnClickListener(this); 189 } 190 191 private void addSeparator() { 192 View sep = makeDividerView(); 193 sep.setLayoutParams(makeDividerLayoutParams()); 194 addView(sep); 195 } 196 197 private ImageView makeDividerView() { 198 ImageView result = new ImageView(mContext); 199 result.setImageDrawable(mSeparatorDrawable); 200 result.setScaleType(ImageView.ScaleType.FIT_XY); 201 return result; 202 } 203 204 private LayoutParams makeDividerLayoutParams() { 205 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT, 206 LayoutParams.MATCH_PARENT); 207 params.topMargin = (int) mDividerPadding; 208 params.bottomMargin = (int) mDividerPadding; 209 return params; 210 } 211 212 private void pop(boolean notify) { 213 int n = mCrumbs.size(); 214 if (n > 0) { 215 removeLastView(); 216 if (!mUseBackButton || (n > 1)) { 217 // remove separator 218 removeLastView(); 219 } 220 mCrumbs.remove(n - 1); 221 if (mUseBackButton) { 222 Crumb top = getTopCrumb(); 223 if (top != null && top.canGoBack) { 224 mBackButton.setVisibility(View.VISIBLE); 225 } else { 226 mBackButton.setVisibility(View.GONE); 227 } 228 } 229 updateVisible(); 230 if (notify) { 231 notifyController(); 232 } 233 } 234 } 235 236 private void updateVisible() { 237 // start at index 1 (0 == back button) 238 int childIndex = 1; 239 if (mMaxVisible >= 0) { 240 int invisibleCrumbs = size() - mMaxVisible; 241 if (invisibleCrumbs > 0) { 242 int crumbIndex = 0; 243 while (crumbIndex < invisibleCrumbs) { 244 // Set the crumb to GONE. 245 getChildAt(childIndex).setVisibility(View.GONE); 246 childIndex++; 247 // Each crumb is followed by a separator (except the last 248 // one). Also make it GONE 249 if (getChildAt(childIndex) != null) { 250 getChildAt(childIndex).setVisibility(View.GONE); 251 } 252 childIndex++; 253 // Move to the next crumb. 254 crumbIndex++; 255 } 256 } 257 // Make sure the last two are visible. 258 int childCount = getChildCount(); 259 while (childIndex < childCount) { 260 getChildAt(childIndex).setVisibility(View.VISIBLE); 261 childIndex++; 262 } 263 } else { 264 int count = getChildCount(); 265 for (int i = childIndex; i < count ; i++) { 266 getChildAt(i).setVisibility(View.VISIBLE); 267 } 268 } 269 if (mUseBackButton) { 270 boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false; 271 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE); 272 } else { 273 mBackButton.setVisibility(View.GONE); 274 } 275 } 276 277 private void removeLastView() { 278 int ix = getChildCount(); 279 if (ix > 0) { 280 removeViewAt(ix-1); 281 } 282 } 283 284 Crumb getTopCrumb() { 285 Crumb crumb = null; 286 if (mCrumbs.size() > 0) { 287 crumb = mCrumbs.get(mCrumbs.size() - 1); 288 } 289 return crumb; 290 } 291 292 @Override 293 public void onClick(View v) { 294 if (mBackButton == v) { 295 popView(); 296 notifyController(); 297 } else { 298 // pop until view matches crumb view 299 while (v != getTopCrumb().crumbView) { 300 pop(false); 301 } 302 notifyController(); 303 } 304 } 305 @Override 306 public int getBaseline() { 307 int ix = getChildCount(); 308 if (ix > 0) { 309 // If there is at least one crumb, the baseline will be its 310 // baseline. 311 return getChildAt(ix-1).getBaseline(); 312 } 313 return super.getBaseline(); 314 } 315 316 @Override 317 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 318 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 319 int height = mSeparatorDrawable.getIntrinsicHeight(); 320 if (getMeasuredHeight() < height) { 321 // This should only be an issue if there are currently no separators 322 // showing; i.e. if there is one crumb and no back button. 323 int mode = View.MeasureSpec.getMode(heightMeasureSpec); 324 switch(mode) { 325 case View.MeasureSpec.AT_MOST: 326 if (View.MeasureSpec.getSize(heightMeasureSpec) < height) { 327 return; 328 } 329 break; 330 case View.MeasureSpec.EXACTLY: 331 return; 332 default: 333 break; 334 } 335 setMeasuredDimension(getMeasuredWidth(), height); 336 } 337 } 338 339 class Crumb { 340 341 public View crumbView; 342 public boolean canGoBack; 343 public Object data; 344 345 public Crumb(String title, boolean backEnabled, Object tag) { 346 init(makeCrumbView(title), backEnabled, tag); 347 } 348 349 public Crumb(View view, boolean backEnabled, Object tag) { 350 init(view, backEnabled, tag); 351 } 352 353 private void init(View view, boolean back, Object tag) { 354 canGoBack = back; 355 crumbView = view; 356 data = tag; 357 } 358 359 private TextView makeCrumbView(String name) { 360 TextView tv = new TextView(mContext); 361 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium); 362 tv.setPadding(16, 0, 16, 0); 363 tv.setGravity(Gravity.CENTER_VERTICAL); 364 tv.setText(name); 365 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, 366 LayoutParams.MATCH_PARENT)); 367 tv.setMaxWidth(mContext.getResources().getInteger( 368 R.integer.max_width_crumb)); 369 tv.setMaxLines(1); 370 tv.setEllipsize(TextUtils.TruncateAt.END); 371 return tv; 372 } 373 374 } 375 376} 377