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