blob: c3bff59fce41925a1af700b7dfd86c548ea5dffc [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;
kaiyiz3d7e4d52013-09-17 17:56:27 +080031import android.widget.RelativeLayout;
Michael Kolb370a4f32010-10-06 10:45:32 -070032import android.widget.TextView;
33
34import java.util.ArrayList;
35import java.util.List;
36
37/**
38 * Simple bread crumb view
39 * Use setController to receive callbacks from user interactions
40 * Use pushView, popView, clear, and getTopData to change/access the view stack
41 */
kaiyiz3d7e4d52013-09-17 17:56:27 +080042public class BreadCrumbView extends RelativeLayout implements OnClickListener {
John Reckb3417f02011-01-14 11:01:05 -080043 private static final int DIVIDER_PADDING = 12; // dips
John Reck95f88e42011-09-26 09:25:43 -070044 private static final int CRUMB_PADDING = 8; // dips
Michael Kolb370a4f32010-10-06 10:45:32 -070045
John Reck71efc2b2011-05-09 16:54:28 -070046 public interface Controller {
47 public void onTop(BreadCrumbView view, int level, Object data);
Michael Kolb370a4f32010-10-06 10:45:32 -070048 }
49
50 private ImageButton mBackButton;
kaiyiz3d7e4d52013-09-17 17:56:27 +080051 private LinearLayout mCrumbLayout;
52 private LinearLayout mBackLayout;
Michael Kolb370a4f32010-10-06 10:45:32 -070053 private Controller mController;
54 private List<Crumb> mCrumbs;
55 private boolean mUseBackButton;
Leon Scroggins7e5f7352010-10-18 13:25:31 -040056 private Drawable mSeparatorDrawable;
John Reckb3417f02011-01-14 11:01:05 -080057 private float mDividerPadding;
John Reck89f73c12010-12-01 10:10:14 -080058 private int mMaxVisible = -1;
John Reck71efc2b2011-05-09 16:54:28 -070059 private Context mContext;
John Reck95f88e42011-09-26 09:25:43 -070060 private int mCrumbPadding;
Michael Kolb370a4f32010-10-06 10:45:32 -070061
62 /**
63 * @param context
64 * @param attrs
65 * @param defStyle
66 */
67 public BreadCrumbView(Context context, AttributeSet attrs, int defStyle) {
68 super(context, attrs, defStyle);
69 init(context);
70 }
71
72 /**
73 * @param context
74 * @param attrs
75 */
76 public BreadCrumbView(Context context, AttributeSet attrs) {
77 super(context, attrs);
78 init(context);
79 }
80
81 /**
82 * @param context
83 */
84 public BreadCrumbView(Context context) {
85 super(context);
86 init(context);
87 }
88
89 private void init(Context ctx) {
John Reck71efc2b2011-05-09 16:54:28 -070090 mContext = ctx;
91 setFocusable(true);
kaiyiz3d7e4d52013-09-17 17:56:27 +080092 setGravity(Gravity.CENTER_VERTICAL);
Michael Kolb370a4f32010-10-06 10:45:32 -070093 mUseBackButton = false;
94 mCrumbs = new ArrayList<Crumb>();
John Reck71efc2b2011-05-09 16:54:28 -070095 TypedArray a = mContext.obtainStyledAttributes(com.android.internal.R.styleable.Theme);
John Reckb3417f02011-01-14 11:01:05 -080096 mSeparatorDrawable = a.getDrawable(com.android.internal.R.styleable.Theme_dividerVertical);
97 a.recycle();
John Reck95f88e42011-09-26 09:25:43 -070098 float density = mContext.getResources().getDisplayMetrics().density;
99 mDividerPadding = DIVIDER_PADDING * density;
100 mCrumbPadding = (int) (CRUMB_PADDING * density);
kaiyiz3d7e4d52013-09-17 17:56:27 +0800101 addCrumbLayout();
102 addBackLayout();
Michael Kolb370a4f32010-10-06 10:45:32 -0700103 }
104
105 public void setUseBackButton(boolean useflag) {
106 mUseBackButton = useflag;
John Reck89f73c12010-12-01 10:10:14 -0800107 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700108 }
109
110 public void setController(Controller ctl) {
111 mController = ctl;
112 }
113
John Reck89f73c12010-12-01 10:10:14 -0800114 public int getMaxVisible() {
115 return mMaxVisible;
116 }
117
118 public void setMaxVisible(int max) {
119 mMaxVisible = max;
120 updateVisible();
121 }
122
123 public int getTopLevel() {
124 return mCrumbs.size();
125 }
126
Michael Kolb370a4f32010-10-06 10:45:32 -0700127 public Object getTopData() {
128 Crumb c = getTopCrumb();
129 if (c != null) {
130 return c.data;
131 }
132 return null;
133 }
134
135 public int size() {
136 return mCrumbs.size();
137 }
138
139 public void clear() {
140 while (mCrumbs.size() > 1) {
141 pop(false);
142 }
143 pop(true);
144 }
145
146 public void notifyController() {
147 if (mController != null) {
148 if (mCrumbs.size() > 0) {
John Reck71efc2b2011-05-09 16:54:28 -0700149 mController.onTop(this, mCrumbs.size(), getTopCrumb().data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700150 } else {
John Reck71efc2b2011-05-09 16:54:28 -0700151 mController.onTop(this, 0, null);
Michael Kolb370a4f32010-10-06 10:45:32 -0700152 }
153 }
154 }
155
Leon Scroggins905250c2010-12-17 15:25:33 -0500156 public View pushView(String name, Object data) {
157 return pushView(name, true, data);
Leon Scroggins74dbe012010-10-15 10:54:27 -0400158 }
159
Leon Scroggins905250c2010-12-17 15:25:33 -0500160 public View pushView(String name, boolean canGoBack, Object data) {
Leon Scroggins74dbe012010-10-15 10:54:27 -0400161 Crumb crumb = new Crumb(name, canGoBack, data);
Michael Kolb370a4f32010-10-06 10:45:32 -0700162 pushCrumb(crumb);
Leon Scroggins905250c2010-12-17 15:25:33 -0500163 return crumb.crumbView;
Michael Kolb370a4f32010-10-06 10:45:32 -0700164 }
165
166 public void pushView(View view, Object data) {
167 Crumb crumb = new Crumb(view, true, data);
168 pushCrumb(crumb);
169 }
170
171 public void popView() {
172 pop(true);
173 }
174
175 private void addBackButton() {
176 mBackButton = new ImageButton(mContext);
kaiyiz3d7e4d52013-09-17 17:56:27 +0800177 mBackButton.setImageResource(R.drawable.icon_up);
Michael Kolb5a72f182011-01-13 20:35:06 -0800178 TypedValue outValue = new TypedValue();
179 getContext().getTheme().resolveAttribute(
180 android.R.attr.selectableItemBackground, outValue, true);
181 int resid = outValue.resourceId;
182 mBackButton.setBackgroundResource(resid);
kaiyiz3d7e4d52013-09-17 17:56:27 +0800183 mBackButton.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
Michael Kolb370a4f32010-10-06 10:45:32 -0700184 mBackButton.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
185 LayoutParams.MATCH_PARENT));
186 mBackButton.setOnClickListener(this);
John Reckaac2ce02012-09-21 14:17:47 -0700187 mBackButton.setContentDescription(mContext.getText(
188 R.string.accessibility_button_bookmarks_folder_up));
kaiyiz3d7e4d52013-09-17 17:56:27 +0800189 mBackLayout.addView(mBackButton);
190 }
191
192 private void addParentLabel() {
193 TextView tv = new TextView(mContext);
194 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
195 tv.setPadding(mCrumbPadding, 0, 0, 0);
196 tv.setGravity(Gravity.CENTER_VERTICAL);
197 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
198 LayoutParams.WRAP_CONTENT));
199 tv.setText("/ .../");
200 tv.setSingleLine();
201 tv.setVisibility(View.GONE);
202 mCrumbLayout.addView(tv);
203 }
204
205 private void addCrumbLayout() {
206 mCrumbLayout = new LinearLayout(mContext);
207 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
208 LayoutParams.WRAP_CONTENT);
209 params.addRule(ALIGN_PARENT_LEFT, TRUE);
210 params.setMargins(0, 0, 4 * mCrumbPadding, 0);
211 mCrumbLayout.setLayoutParams(params);
212 mCrumbLayout.setVisibility(View.VISIBLE);
213 addParentLabel();
214 addView(mCrumbLayout);
215 }
216
217 private void addBackLayout() {
218 mBackLayout= new LinearLayout(mContext);
219 LayoutParams params = new LayoutParams(LayoutParams.WRAP_CONTENT,
220 LayoutParams.WRAP_CONTENT);
221 params.addRule(ALIGN_PARENT_RIGHT, TRUE);
222 mBackLayout.setLayoutParams(params);
223 mBackLayout.setVisibility(View.GONE);
224 addSeparator();
225 addBackButton();
226 addView(mBackLayout);
Michael Kolb370a4f32010-10-06 10:45:32 -0700227 }
228
229 private void pushCrumb(Crumb crumb) {
Michael Kolb370a4f32010-10-06 10:45:32 -0700230 mCrumbs.add(crumb);
kaiyiz3d7e4d52013-09-17 17:56:27 +0800231 mCrumbLayout.addView(crumb.crumbView);
John Reck89f73c12010-12-01 10:10:14 -0800232 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700233 crumb.crumbView.setOnClickListener(this);
234 }
235
236 private void addSeparator() {
John Reckb3417f02011-01-14 11:01:05 -0800237 View sep = makeDividerView();
238 sep.setLayoutParams(makeDividerLayoutParams());
kaiyiz3d7e4d52013-09-17 17:56:27 +0800239 mBackLayout.addView(sep);
Michael Kolb370a4f32010-10-06 10:45:32 -0700240 }
241
John Reckb3417f02011-01-14 11:01:05 -0800242 private ImageView makeDividerView() {
243 ImageView result = new ImageView(mContext);
244 result.setImageDrawable(mSeparatorDrawable);
245 result.setScaleType(ImageView.ScaleType.FIT_XY);
246 return result;
247 }
248
kaiyiz3d7e4d52013-09-17 17:56:27 +0800249 private LinearLayout.LayoutParams makeDividerLayoutParams() {
250 LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
251 LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
John Reckb3417f02011-01-14 11:01:05 -0800252 return params;
253 }
254
Michael Kolb370a4f32010-10-06 10:45:32 -0700255 private void pop(boolean notify) {
256 int n = mCrumbs.size();
257 if (n > 0) {
258 removeLastView();
Michael Kolb370a4f32010-10-06 10:45:32 -0700259 mCrumbs.remove(n - 1);
260 if (mUseBackButton) {
261 Crumb top = getTopCrumb();
Leon Scroggins8a2a0e72010-10-15 15:49:40 -0400262 if (top != null && top.canGoBack) {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800263 mBackLayout.setVisibility(View.VISIBLE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700264 } else {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800265 mBackLayout.setVisibility(View.GONE);
Michael Kolb370a4f32010-10-06 10:45:32 -0700266 }
267 }
John Reck89f73c12010-12-01 10:10:14 -0800268 updateVisible();
Michael Kolb370a4f32010-10-06 10:45:32 -0700269 if (notify) {
270 notifyController();
271 }
272 }
273 }
274
John Reck89f73c12010-12-01 10:10:14 -0800275 private void updateVisible() {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800276 // start at index 1 (0 == parent label)
John Reck89f73c12010-12-01 10:10:14 -0800277 int childIndex = 1;
278 if (mMaxVisible >= 0) {
279 int invisibleCrumbs = size() - mMaxVisible;
280 if (invisibleCrumbs > 0) {
281 int crumbIndex = 0;
282 while (crumbIndex < invisibleCrumbs) {
283 // Set the crumb to GONE.
kaiyiz3d7e4d52013-09-17 17:56:27 +0800284 mCrumbLayout.getChildAt(childIndex).setVisibility(View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800285 childIndex++;
286 // Move to the next crumb.
287 crumbIndex++;
288 }
289 }
kaiyiz3d7e4d52013-09-17 17:56:27 +0800290 // Make sure the last is visible.
291 int childCount = mCrumbLayout.getChildCount();
John Reck89f73c12010-12-01 10:10:14 -0800292 while (childIndex < childCount) {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800293 mCrumbLayout.getChildAt(childIndex).setVisibility(View.VISIBLE);
John Reck89f73c12010-12-01 10:10:14 -0800294 childIndex++;
295 }
296 } else {
297 int count = getChildCount();
298 for (int i = childIndex; i < count ; i++) {
299 getChildAt(i).setVisibility(View.VISIBLE);
300 }
301 }
302 if (mUseBackButton) {
303 boolean canGoBack = getTopCrumb() != null ? getTopCrumb().canGoBack : false;
kaiyiz3d7e4d52013-09-17 17:56:27 +0800304 mBackLayout.setVisibility(canGoBack ? View.VISIBLE : View.GONE);
305 if (canGoBack) {
306 mCrumbLayout.getChildAt(0).setVisibility(VISIBLE);
307 } else {
308 mCrumbLayout.getChildAt(0).setVisibility(GONE);
309 }
John Reck89f73c12010-12-01 10:10:14 -0800310 } else {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800311 mBackLayout.setVisibility(View.GONE);
John Reck89f73c12010-12-01 10:10:14 -0800312 }
313 }
314
Michael Kolb370a4f32010-10-06 10:45:32 -0700315 private void removeLastView() {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800316 int ix = mCrumbLayout.getChildCount();
Michael Kolb370a4f32010-10-06 10:45:32 -0700317 if (ix > 0) {
kaiyiz3d7e4d52013-09-17 17:56:27 +0800318 mCrumbLayout.removeViewAt(ix-1);
Michael Kolb370a4f32010-10-06 10:45:32 -0700319 }
320 }
321
John Reckd4893b02010-12-07 17:38:34 -0800322 Crumb getTopCrumb() {
Michael Kolb370a4f32010-10-06 10:45:32 -0700323 Crumb crumb = null;
324 if (mCrumbs.size() > 0) {
325 crumb = mCrumbs.get(mCrumbs.size() - 1);
326 }
327 return crumb;
328 }
329
330 @Override
331 public void onClick(View v) {
332 if (mBackButton == v) {
333 popView();
334 notifyController();
335 } else {
336 // pop until view matches crumb view
337 while (v != getTopCrumb().crumbView) {
338 pop(false);
339 }
340 notifyController();
341 }
342 }
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400343 @Override
344 public int getBaseline() {
345 int ix = getChildCount();
346 if (ix > 0) {
347 // If there is at least one crumb, the baseline will be its
348 // baseline.
349 return getChildAt(ix-1).getBaseline();
350 }
351 return super.getBaseline();
352 }
353
354 @Override
355 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
356 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
357 int height = mSeparatorDrawable.getIntrinsicHeight();
Dianne Hackborn7831da92010-12-03 00:19:01 -0800358 if (getMeasuredHeight() < height) {
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400359 // This should only be an issue if there are currently no separators
360 // showing; i.e. if there is one crumb and no back button.
361 int mode = View.MeasureSpec.getMode(heightMeasureSpec);
362 switch(mode) {
363 case View.MeasureSpec.AT_MOST:
364 if (View.MeasureSpec.getSize(heightMeasureSpec) < height) {
365 return;
366 }
367 break;
368 case View.MeasureSpec.EXACTLY:
369 return;
370 default:
371 break;
372 }
Dianne Hackborn7831da92010-12-03 00:19:01 -0800373 setMeasuredDimension(getMeasuredWidth(), height);
Leon Scroggins7e5f7352010-10-18 13:25:31 -0400374 }
375 }
Michael Kolb370a4f32010-10-06 10:45:32 -0700376
377 class Crumb {
378
379 public View crumbView;
380 public boolean canGoBack;
381 public Object data;
382
383 public Crumb(String title, boolean backEnabled, Object tag) {
384 init(makeCrumbView(title), backEnabled, tag);
385 }
386
387 public Crumb(View view, boolean backEnabled, Object tag) {
388 init(view, backEnabled, tag);
389 }
390
391 private void init(View view, boolean back, Object tag) {
392 canGoBack = back;
393 crumbView = view;
394 data = tag;
395 }
396
397 private TextView makeCrumbView(String name) {
398 TextView tv = new TextView(mContext);
399 tv.setTextAppearance(mContext, android.R.style.TextAppearance_Medium);
John Reck95f88e42011-09-26 09:25:43 -0700400 tv.setPadding(mCrumbPadding, 0, mCrumbPadding, 0);
Michael Kolb370a4f32010-10-06 10:45:32 -0700401 tv.setGravity(Gravity.CENTER_VERTICAL);
402 tv.setText(name);
403 tv.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
404 LayoutParams.MATCH_PARENT));
John Reck95f88e42011-09-26 09:25:43 -0700405 tv.setSingleLine();
Leon Scroggins74dbe012010-10-15 10:54:27 -0400406 tv.setEllipsize(TextUtils.TruncateAt.END);
Michael Kolb370a4f32010-10-06 10:45:32 -0700407 return tv;
408 }
409
410 }
411
412}