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