blob: 6706deb3fd73ef9981104a6761db4ae523cb83f8 [file] [log] [blame]
Michael Kolb370a4f32010-10-06 10:45:32 -07001/*
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;
John Reckb3417f02011-01-14 11:01:05 -080020import android.content.res.TypedArray;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040021import android.graphics.drawable.Drawable;
Leon Scroggins74dbe012010-10-15 10:54:27 -040022import android.text.TextUtils;
Michael Kolb370a4f32010-10-06 10:45:32 -070023import android.util.AttributeSet;
Michael Kolb5a72f182011-01-13 20:35:06 -080024import android.util.TypedValue;
Michael Kolb370a4f32010-10-06 10:45:32 -070025import 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 {
John Reckb3417f02011-01-14 11:01:05 -080042 private static final int DIVIDER_PADDING = 12; // dips
Michael Kolb370a4f32010-10-06 10:45:32 -070043
John Reck71efc2b2011-05-09 16:54:28 -070044 public interface Controller {
45 public void onTop(BreadCrumbView view, int level, Object data);
Michael Kolb370a4f32010-10-06 10:45:32 -070046 }
47
48 private ImageButton mBackButton;
49 private Controller mController;
50 private List<Crumb> mCrumbs;
51 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040052 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080053 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080054 private int mMaxVisible = -1;
John Reck71efc2b2011-05-09 16:54:28 -070055 private Context mContext;
Michael Kolb370a4f32010-10-06 10:45:32 -070056
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) {
John Reck71efc2b2011-05-09 16:54:28 -070085 mContext = ctx;
86 setFocusable(true);
Michael Kolb370a4f32010-10-06 10:45:32 -070087 mUseBackButton = false;
88 mCrumbs = new ArrayList<Crumb>();
John Reck71efc2b2011-05-09 16:54:28 -070089 TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
John Reckb3417f02011-01-14 11:01:05 -080090 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
91 a.recycle();
John Reck71efc2b2011-05-09 16:54:28 -070092 mDividerPadding = DIVIDER_PADDING * mContext.getResources().getDisplayMetrics().density;
John Reck89f73c12010-12-01 10:10:14 -080093 addBackButton();
Michael Kolb370a4f32010-10-06 10:45:32 -070094 }
95
96 public void setUseBackButton(boolean useflag) {
97 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -080098 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -070099 }
100
101 public void setController(Controller ctl) {
102 mController = ctl;
103 }
104
John Reck89f73c12010-12-01 10:10:14 -0800105 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
Michael Kolb370a4f32010-10-06 10:45:32 -0700118 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) {
John Reck71efc2b2011-05-09 16:54:28 -0700140 mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700141 } else {
John Reck71efc2b2011-05-09 16:54:28 -0700142 mController.onTop(this, 0, null);
Michael Kolb370a4f32010-10-06 10:45:32 -0700143 }
144 }
145 }
146
Leon Scroggins905250c2010-12-17 15:25:33 -0500147 public View pushView(String name, Object data) {
148 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400149 }
150
Leon Scroggins905250c2010-12-17 15:25:33 -0500151 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400152 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700153 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500154 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700155 }
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);
Michael Kolb5a72f182011-01-13 20:35:06 -0800168 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);
Michael Kolb370a4f32010-10-06 10:45:32 -0700174 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
175 LayoutParams.MATCH_PARENT));
176 mBackButton.setOnClickListener(this);
Leon Scroggins905250c2010-12-17 15:25:33 -0500177 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700178 addView(mBackButton, 0);
179 }
180
181 private void pushCrumb(Crumb crumb) {
John Reck89f73c12010-12-01 10:10:14 -0800182 if (mCrumbs.size() > 0) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700183 addSeparator();
184 }
185 mCrumbs.add(crumb);
186 addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800187 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700188 crumb.crumbView.setOnClickListener(this);
189 }
190
191 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800192 View sep = makeDividerView();
193 sep.setLayoutParams(makeDividerLayoutParams());
Michael Kolb370a4f32010-10-06 10:45:32 -0700194 addView(sep);
195 }
196
John Reckb3417f02011-01-14 11:01:05 -0800197 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
Michael Kolb370a4f32010-10-06 10:45:32 -0700212 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();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400223 if (top != null && top.canGoBack) {
224 mBackButton.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700225 } else {
Leon Scroggins905250c2010-12-17 15:25:33 -0500226 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700227 }
228 }
John Reck89f73c12010-12-01 10:10:14 -0800229 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700230 if (notify) {
231 notifyController();
232 }
233 }
234 }
235
John Reck89f73c12010-12-01 10:10:14 -0800236 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;
Leon Scroggins905250c2010-12-17 15:25:33 -0500271 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800272 } else {
273 mBackButton.setVisibility(View.GONE);
274 }
275 }
276
Michael Kolb370a4f32010-10-06 10:45:32 -0700277 private void removeLastView() {
278 int ix = getChildCount();
279 if (ix > 0) {
280 removeViewAt(ix-1);
281 }
282 }
283
John Reckd4893b02010-12-07 17:38:34 -0800284 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700285 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 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400305 @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();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800320 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400321 // 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 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800335 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400336 }
337 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700338
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));
Leon Scroggins74dbe012010-10-15 10:54:27 -0400367 tv.setMaxWidth(mContext.getResources().getInteger(
368 R.integer.max_width_crumb));
369 tv.setMaxLines(1);
370 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700371 return tv;
372 }
373
374 }
375
376}