blob: aa7774455060e9b488d094a7d2ebad54dd45fd5c [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
John Reck95f88e42011-09-26 09:25:43 -070043 private static final int CRUMB_PADDING = 8; // dips
Michael Kolb370a4f32010-10-06 10:45:32 -070044
John Reck71efc2b2011-05-09 16:54:28 -070045 public interface Controller {
46 public void onTop(BreadCrumbView view, int level, Object data);
Michael Kolb370a4f32010-10-06 10:45:32 -070047 }
48
49 private ImageButton mBackButton;
50 private Controller mController;
51 private List<Crumb> mCrumbs;
52 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040053 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080054 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080055 private int mMaxVisible = -1;
John Reck71efc2b2011-05-09 16:54:28 -070056 private Context mContext;
John Reck95f88e42011-09-26 09:25:43 -070057 private int mCrumbPadding;
Michael Kolb370a4f32010-10-06 10:45:32 -070058
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) {
John Reck71efc2b2011-05-09 16:54:28 -070087 mContext = ctx;
88 setFocusable(true);
Michael Kolb370a4f32010-10-06 10:45:32 -070089 mUseBackButton = false;
90 mCrumbs = new ArrayList<Crumb>();
John Reck71efc2b2011-05-09 16:54:28 -070091 TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
John Reckb3417f02011-01-14 11:01:05 -080092 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
93 a.recycle();
John Reck95f88e42011-09-26 09:25:43 -070094 float density = mContext.getResources().getDisplayMetrics().density;
95 mDividerPadding = DIVIDER_PADDING * density;
96 mCrumbPadding = (int) (CRUMB_PADDING * density);
John Reck89f73c12010-12-01 10:10:14 -080097 addBackButton();
Michael Kolb370a4f32010-10-06 10:45:32 -070098 }
99
100 public void setUseBackButton(boolean useflag) {
101 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -0800102 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700103 }
104
105 public void setController(Controller ctl) {
106 mController = ctl;
107 }
108
John Reck89f73c12010-12-01 10:10:14 -0800109 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
Michael Kolb370a4f32010-10-06 10:45:32 -0700122 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) {
John Reck71efc2b2011-05-09 16:54:28 -0700144 mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700145 } else {
John Reck71efc2b2011-05-09 16:54:28 -0700146 mController.onTop(this, 0, null);
Michael Kolb370a4f32010-10-06 10:45:32 -0700147 }
148 }
149 }
150
Leon Scroggins905250c2010-12-17 15:25:33 -0500151 public View pushView(String name, Object data) {
152 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400153 }
154
Leon Scroggins905250c2010-12-17 15:25:33 -0500155 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400156 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700157 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500158 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700159 }
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);
Michael Kolb5a72f182011-01-13 20:35:06 -0800172 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);
Michael Kolb370a4f32010-10-06 10:45:32 -0700178 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
179 LayoutParams.MATCH_PARENT));
180 mBackButton.setOnClickListener(this);
Leon Scroggins905250c2010-12-17 15:25:33 -0500181 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700182 addView(mBackButton, 0);
183 }
184
185 private void pushCrumb(Crumb crumb) {
John Reck89f73c12010-12-01 10:10:14 -0800186 if (mCrumbs.size() > 0) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700187 addSeparator();
188 }
189 mCrumbs.add(crumb);
190 addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800191 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700192 crumb.crumbView.setOnClickListener(this);
193 }
194
195 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800196 View sep = makeDividerView();
197 sep.setLayoutParams(makeDividerLayoutParams());
Michael Kolb370a4f32010-10-06 10:45:32 -0700198 addView(sep);
199 }
200
John Reckb3417f02011-01-14 11:01:05 -0800201 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
Michael Kolb370a4f32010-10-06 10:45:32 -0700216 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();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400227 if (top != null && top.canGoBack) {
228 mBackButton.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700229 } else {
Leon Scroggins905250c2010-12-17 15:25:33 -0500230 mBackButton.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700231 }
232 }
John Reck89f73c12010-12-01 10:10:14 -0800233 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700234 if (notify) {
235 notifyController();
236 }
237 }
238 }
239
John Reck89f73c12010-12-01 10:10:14 -0800240 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;
Leon Scroggins905250c2010-12-17 15:25:33 -0500275 mBackButton.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800276 } else {
277 mBackButton.setVisibility(View.GONE);
278 }
279 }
280
Michael Kolb370a4f32010-10-06 10:45:32 -0700281 private void removeLastView() {
282 int ix = getChildCount();
283 if (ix > 0) {
284 removeViewAt(ix-1);
285 }
286 }
287
John Reckd4893b02010-12-07 17:38:34 -0800288 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700289 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 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400309 @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();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800324 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400325 // 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 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800339 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400340 }
341 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700342
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);
John Reck95f88e42011-09-26 09:25:43 -0700366 tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
Michael Kolb370a4f32010-10-06 10:45:32 -0700367 tv.setGravity(Gravity.CENTER_VERTICAL);
368 tv.setText(name);
369 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
370 LayoutParams.MATCH_PARENT));
John Reck95f88e42011-09-26 09:25:43 -0700371 tv.setSingleLine();
Leon Scroggins74dbe012010-10-15 10:54:27 -0400372 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700373 return tv;
374 }
375
376 }
377
378}