• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {
onTop(BreadCrumbView view, int level, Object data)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      */
BreadCrumbView(Context context, AttributeSet attrs, int defStyle)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      */
BreadCrumbView(Context context, AttributeSet attrs)73     public BreadCrumbView(Context context, AttributeSet attrs) {
74         super(context, attrs);
75         init(context);
76     }
77 
78     /**
79      * @param context
80      */
BreadCrumbView(Context context)81     public BreadCrumbView(Context context) {
82         super(context);
83         init(context);
84     }
85 
init(Context ctx)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 
setUseBackButton(boolean useflag)100     public void setUseBackButton(boolean useflag) {
101         mUseBackButton = useflag;
102         updateVisible();
103     }
104 
setController(Controller ctl)105     public void setController(Controller ctl) {
106         mController = ctl;
107     }
108 
getMaxVisible()109     public int getMaxVisible() {
110         return mMaxVisible;
111     }
112 
setMaxVisible(int max)113     public void setMaxVisible(int max) {
114         mMaxVisible = max;
115         updateVisible();
116     }
117 
getTopLevel()118     public int getTopLevel() {
119         return mCrumbs.size();
120     }
121 
getTopData()122     public Object getTopData() {
123         Crumb c = getTopCrumb();
124         if (c != null) {
125             return c.data;
126         }
127         return null;
128     }
129 
size()130     public int size() {
131         return mCrumbs.size();
132     }
133 
clear()134     public void clear() {
135         while (mCrumbs.size() > 1) {
136             pop(false);
137         }
138         pop(true);
139     }
140 
notifyController()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 
pushView(String name, Object data)151     public View pushView(String name, Object data) {
152         return pushView(name, true, data);
153     }
154 
pushView(String name, boolean canGoBack, Object data)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 
pushView(View view, Object data)161     public void pushView(View view, Object data) {
162         Crumb crumb = new Crumb(view, true, data);
163         pushCrumb(crumb);
164     }
165 
popView()166     public void popView() {
167         pop(true);
168     }
169 
addBackButton()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 
pushCrumb(Crumb crumb)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 
addSeparator()195     private void addSeparator() {
196         View sep = makeDividerView();
197         sep.setLayoutParams(makeDividerLayoutParams());
198         addView(sep);
199     }
200 
makeDividerView()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 
makeDividerLayoutParams()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 
pop(boolean notify)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 
updateVisible()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 
removeLastView()281     private void removeLastView() {
282         int ix = getChildCount();
283         if (ix > 0) {
284             removeViewAt(ix-1);
285         }
286     }
287 
getTopCrumb()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
onClick(View v)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
getBaseline()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
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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 
Crumb(String title, boolean backEnabled, Object tag)349         public Crumb(String title, boolean backEnabled, Object tag) {
350             init(makeCrumbView(title), backEnabled, tag);
351         }
352 
Crumb(View view, boolean backEnabled, Object tag)353         public Crumb(View view, boolean backEnabled, Object tag) {
354             init(view, backEnabled, tag);
355         }
356 
init(View view, boolean back, Object tag)357         private void init(View view, boolean back, Object tag) {
358             canGoBack = back;
359             crumbView = view;
360             data = tag;
361         }
362 
makeCrumbView(String name)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